mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged HEAD-QA to HEAD (4.2) (including moving test classes into separate folders)
51903 to 54309 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@54310 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -40,26 +40,14 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
* just need to provide buildCache(String tenanaId)
|
||||
*
|
||||
* @author Andy
|
||||
* @since 4.1.3
|
||||
*/
|
||||
public abstract class AbstractAsynchronouslyRefreshedCache<T> implements AsynchronouslyRefreshedCache<T>, RefreshableCacheListener, Callable<Void>, BeanNameAware,
|
||||
InitializingBean, TransactionListener
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AbstractAsynchronouslyRefreshedCache.class);
|
||||
|
||||
private static final String RESOURCE_KEY_TXN_DATA = "AbstractAsynchronouslyRefreshedCache.TxnData";
|
||||
|
||||
private List<RefreshableCacheListener> listeners = new LinkedList<RefreshableCacheListener>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.cache.AsynchronouslyRefreshedCacheRegistry#register(org.alfresco.repo.cache.
|
||||
* RefreshableCacheListener)
|
||||
*/
|
||||
@Override
|
||||
public void register(RefreshableCacheListener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
private static Log logger = LogFactory.getLog(AbstractAsynchronouslyRefreshedCache.class);
|
||||
|
||||
private enum RefreshState
|
||||
{
|
||||
@@ -67,29 +55,27 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
};
|
||||
|
||||
private ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
private AsynchronouslyRefreshedCacheRegistry registry;
|
||||
|
||||
private TenantService tenantService;
|
||||
|
||||
// State
|
||||
|
||||
private List<RefreshableCacheListener> listeners = new LinkedList<RefreshableCacheListener>();
|
||||
private final ReentrantReadWriteLock liveLock = new ReentrantReadWriteLock();
|
||||
|
||||
private final ReentrantReadWriteLock refreshLock = new ReentrantReadWriteLock();
|
||||
|
||||
private final ReentrantReadWriteLock runLock = new ReentrantReadWriteLock();
|
||||
|
||||
private HashMap<String, T> live = new HashMap<String, T>();
|
||||
|
||||
private LinkedHashSet<Refresh> refreshQueue = new LinkedHashSet<Refresh>();
|
||||
|
||||
private String cacheId;
|
||||
|
||||
private RefreshState refreshState = RefreshState.IDLE;
|
||||
|
||||
private String resourceKeyTxnData;
|
||||
|
||||
@Override
|
||||
public void register(RefreshableCacheListener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param threadPool
|
||||
* the threadPool to set
|
||||
@@ -122,10 +108,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
registry.register(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.cache.RefreshableCache#get()
|
||||
*/
|
||||
@Override
|
||||
public T get()
|
||||
{
|
||||
@@ -232,10 +214,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.cache.RefreshableCache#refresh()
|
||||
*/
|
||||
@Override
|
||||
public void refresh()
|
||||
{
|
||||
@@ -247,10 +225,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
registry.broadcastEvent(new RefreshableCacheRefreshEvent(cacheId, tenantId), true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.cache.RefreshableCacheListener#onRefreshableCacheEvent()
|
||||
*/
|
||||
@Override
|
||||
public void onRefreshableCacheEvent(RefreshableCacheEvent refreshableCacheEvent)
|
||||
{
|
||||
@@ -304,6 +278,10 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
private void queueRefreshAndSubmit(LinkedHashSet<String> tenantIds)
|
||||
{
|
||||
if((tenantIds == null) || (tenantIds.size() == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
refreshLock.writeLock().lock();
|
||||
try
|
||||
{
|
||||
@@ -323,9 +301,7 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
submit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isUpToDate()
|
||||
{
|
||||
String tenantId = tenantService.getCurrentUserDomain();
|
||||
@@ -354,8 +330,9 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Must be run with runLock.writeLock
|
||||
/**
|
||||
* Must be run with runLock.writeLock
|
||||
*/
|
||||
private Refresh getNextRefresh()
|
||||
{
|
||||
if (runLock.writeLock().isHeldByCurrentThread())
|
||||
@@ -376,7 +353,9 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
}
|
||||
|
||||
// Must be run with runLock.writeLock
|
||||
/**
|
||||
* Must be run with runLock.writeLock
|
||||
*/
|
||||
private int countWaiting()
|
||||
{
|
||||
int count = 0;
|
||||
@@ -427,10 +406,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
@Override
|
||||
public Void call()
|
||||
{
|
||||
@@ -441,7 +416,7 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.warn("Cache update faiiled", e);
|
||||
logger.error("Cache update failed (" + this.getCacheId() + ").", e);
|
||||
runLock.writeLock().lock();
|
||||
try
|
||||
{
|
||||
@@ -456,10 +431,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private void doCall() throws Exception
|
||||
{
|
||||
Refresh refresh = setUpRefresh();
|
||||
@@ -484,10 +455,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refresh
|
||||
* @return
|
||||
*/
|
||||
private void doRefresh(Refresh refresh)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
@@ -515,6 +482,8 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
logger.debug("Cache entry updated for tenant" + refresh.getTenantId());
|
||||
}
|
||||
|
||||
broadcastEvent(new RefreshableCacheRefreshedEvent(cacheId, refresh.tenantId));
|
||||
|
||||
runLock.writeLock().lock();
|
||||
try
|
||||
{
|
||||
@@ -550,7 +519,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
{
|
||||
runLock.writeLock().unlock();
|
||||
}
|
||||
broadcastEvent(new RefreshableCacheRefreshedEvent(cacheId, refresh.tenantId));
|
||||
}
|
||||
|
||||
private Refresh setUpRefresh() throws Exception
|
||||
@@ -602,10 +570,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void setBeanName(String name)
|
||||
{
|
||||
@@ -613,10 +577,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.cache.AsynchronouslyRefreshedCache#getCacheId()
|
||||
*/
|
||||
@Override
|
||||
public String getCacheId()
|
||||
{
|
||||
@@ -625,9 +585,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
/**
|
||||
* Build the cache entry for the specific tenant.
|
||||
*
|
||||
* @param tenantId
|
||||
* @return
|
||||
*/
|
||||
protected abstract T buildCache(String tenantId);
|
||||
|
||||
@@ -667,10 +624,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
@@ -681,10 +634,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
@@ -707,10 +656,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
@@ -719,10 +664,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception
|
||||
{
|
||||
@@ -749,10 +690,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.transaction.TransactionListener#flush()
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void flush()
|
||||
@@ -760,31 +697,18 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
// Nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly)
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion()
|
||||
*/
|
||||
@Override
|
||||
public void beforeCompletion()
|
||||
{
|
||||
// Nothing
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.transaction.TransactionListener#afterCommit()
|
||||
*/
|
||||
@Override
|
||||
public void afterCommit()
|
||||
{
|
||||
@@ -792,10 +716,6 @@ public abstract class AbstractAsynchronouslyRefreshedCache<T> implements Asynchr
|
||||
queueRefreshAndSubmit(txnData.tenantIds);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.transaction.TransactionListener#afterRollback()
|
||||
*/
|
||||
@Override
|
||||
public void afterRollback()
|
||||
{
|
||||
|
@@ -22,16 +22,21 @@ package org.alfresco.repo.cache;
|
||||
* Implementation details in addition to the exposed interface.
|
||||
*
|
||||
* @author Andy
|
||||
*
|
||||
* @since 4.1.3
|
||||
*/
|
||||
public interface AsynchronouslyRefreshedCache<T> extends RefreshableCache<T>
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the cache id
|
||||
*
|
||||
* @return
|
||||
* @return the cache ID
|
||||
*/
|
||||
String getCacheId();
|
||||
|
||||
/**
|
||||
* Determine if the cache is up to date
|
||||
*
|
||||
* @return <tt>true</tt> if the cache is not currently refreshing itself
|
||||
*/
|
||||
boolean isUpToDate();
|
||||
}
|
||||
|
36
source/java/org/alfresco/repo/cache/CacheFactory.java
vendored
Normal file
36
source/java/org/alfresco/repo/cache/CacheFactory.java
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Cache factory interface. Implementing classes create {@link SimpleCache} objects
|
||||
* for a given cache name. It is the responsibility of the implementation to lookup
|
||||
* specific cache configuration details using the supplied name.
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
public interface CacheFactory<K extends Serializable, V>
|
||||
{
|
||||
SimpleCache<K, V> createCache(String cacheName);
|
||||
SimpleCache<K, V> createLocalCache(String cacheName);
|
||||
SimpleCache<K, V> createInvalidatingCache(String cacheName);
|
||||
SimpleCache<K, V> createInvalidateRemovalCache(String cacheName);
|
||||
}
|
1144
source/java/org/alfresco/repo/cache/CacheTest.java
vendored
1144
source/java/org/alfresco/repo/cache/CacheTest.java
vendored
File diff suppressed because it is too large
Load Diff
95
source/java/org/alfresco/repo/cache/DefaultCacheFactory.java
vendored
Normal file
95
source/java/org/alfresco/repo/cache/DefaultCacheFactory.java
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* {@link CacheFactory} implementation that creates {@link DefaultSimpleCache} instances.
|
||||
* The caches are created with a capacity specified by the property {name}.maxItems.
|
||||
* For example, a cache named <tt>cache.ticketsCache</tt> would have a capacity specified
|
||||
* by the property <tt>cache.ticketsCache.maxItems</tt>
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
public class DefaultCacheFactory<K extends Serializable, V> implements CacheFactory<K, V>
|
||||
{
|
||||
private static final Log log = LogFactory.getLog(DefaultCacheFactory.class);
|
||||
private Properties properties;
|
||||
|
||||
@Override
|
||||
public SimpleCache<K, V> createCache(String cacheName)
|
||||
{
|
||||
return createLocalCache(cacheName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleCache<K, V> createLocalCache(String cacheName)
|
||||
{
|
||||
DefaultSimpleCache<K, V> cache = new DefaultSimpleCache<K, V>();
|
||||
cache.setCacheName(cacheName);
|
||||
int maxItems = maxItems(cacheName);
|
||||
// maxItems of zero has no effect, DefaultSimpleCache will use its default capacity.
|
||||
if (maxItems > 0)
|
||||
{
|
||||
cache.setMaxItems(maxItems);
|
||||
}
|
||||
if (log.isDebugEnabled())
|
||||
{
|
||||
log.debug("Creating cache: " + cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleCache<K, V> createInvalidatingCache(String cacheName)
|
||||
{
|
||||
return createLocalCache(cacheName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleCache<K, V> createInvalidateRemovalCache(String cacheName)
|
||||
{
|
||||
return createLocalCache(cacheName);
|
||||
}
|
||||
|
||||
private int maxItems(String cacheName)
|
||||
{
|
||||
String maxItemsStr = properties.getProperty(cacheName + ".maxItems");
|
||||
Integer maxItems = maxItemsStr != null ? Integer.parseInt(maxItemsStr) : 0;
|
||||
return maxItems.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide properties to parameterize cache creation. Cache properties are prefixed
|
||||
* with the cacheName supplied when invoking {@link DefaultCacheFactory#createCache(String)}.
|
||||
* For example, for a cache named cache.ticketsCache the property cache.ticketsCache.maxItems
|
||||
* will determine the capacity of the cache.
|
||||
*
|
||||
* @param properties
|
||||
*/
|
||||
public void setProperties(Properties properties)
|
||||
{
|
||||
this.properties = properties;
|
||||
}
|
||||
}
|
@@ -62,11 +62,13 @@ public class DefaultCacheProvider implements CacheProvider
|
||||
@Override
|
||||
public void start(Properties properties) throws CacheException
|
||||
{
|
||||
log.debug("Starting cache provider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
log.debug("Stopping cache provider");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -127,6 +127,40 @@ public final class DefaultSimpleCache<K extends Serializable, V extends Object>
|
||||
{
|
||||
map.setCapacity(maxItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of items that the cache will hold.
|
||||
*
|
||||
* @return maxItems
|
||||
*/
|
||||
public int getMaxItems()
|
||||
{
|
||||
return map.capacity();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the name of this cache.
|
||||
*
|
||||
* @see #setCacheName(String)
|
||||
* @return the cacheName
|
||||
*/
|
||||
public String getCacheName()
|
||||
{
|
||||
return this.cacheName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since there are many cache instances, it is useful to be able to associate
|
||||
* a name with each one.
|
||||
*
|
||||
* @see #setBeanName(String)
|
||||
* @param cacheName
|
||||
*/
|
||||
public void setCacheName(String cacheName)
|
||||
{
|
||||
this.cacheName = cacheName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since there are many cache instances, it is useful to be able to associate
|
||||
|
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for the DefaultSimpleCache class.
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
public class DefaultSimpleCacheTest
|
||||
{
|
||||
private DefaultSimpleCache<Integer, String> cache;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
cache = new DefaultSimpleCache<Integer, String>(100, getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boundedSizeCache() throws Exception
|
||||
{
|
||||
// We'll only keep the LAST 3 items
|
||||
cache.setMaxItems(3);
|
||||
|
||||
cache.put(1, "1");
|
||||
cache.put(2, "2");
|
||||
cache.put(3, "3");
|
||||
cache.put(4, "4");
|
||||
cache.put(5, "5");
|
||||
|
||||
// Lost the first item
|
||||
assertNull(cache.get(1));
|
||||
assertFalse(cache.contains(1));
|
||||
|
||||
// Lost the second item
|
||||
assertNull(cache.get(2));
|
||||
assertFalse(cache.contains(2));
|
||||
|
||||
// Last three are still present
|
||||
assertEquals("3", cache.get(3));
|
||||
assertEquals("4", cache.get(4));
|
||||
assertEquals("5", cache.get(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStoreNullValues()
|
||||
{
|
||||
cache.put(2, null);
|
||||
assertEquals(null, cache.get(2));
|
||||
// Check that the key has an entry against it.
|
||||
assertTrue(cache.contains(2));
|
||||
|
||||
// Ensure that a key that has not been assigned is discernable
|
||||
// from a key that has been assigned a null value.
|
||||
assertEquals(null, cache.get(4));
|
||||
assertFalse(cache.contains(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRemoveItems()
|
||||
{
|
||||
cache.put(1, "hello");
|
||||
cache.put(2, "world");
|
||||
assertEquals("hello", cache.get(1));
|
||||
assertEquals("world", cache.get(2));
|
||||
|
||||
cache.remove(2);
|
||||
assertEquals("hello", cache.get(1));
|
||||
assertEquals(null, cache.get(2));
|
||||
assertEquals(false, cache.contains(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canClearItems()
|
||||
{
|
||||
cache.put(1, "hello");
|
||||
cache.put(2, "world");
|
||||
assertEquals("hello", cache.get(1));
|
||||
assertEquals("world", cache.get(2));
|
||||
|
||||
cache.clear();
|
||||
|
||||
assertEquals(null, cache.get(1));
|
||||
assertEquals(false, cache.contains(1));
|
||||
assertEquals(null, cache.get(2));
|
||||
assertEquals(false, cache.contains(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canGetKeys()
|
||||
{
|
||||
cache.put(3, "blue");
|
||||
cache.put(12, "red");
|
||||
cache.put(43, "olive");
|
||||
|
||||
List<Integer> keys = new ArrayList<Integer>(cache.getKeys());
|
||||
Collections.sort(keys);
|
||||
|
||||
Iterator<Integer> it = keys.iterator();
|
||||
assertEquals(3, it.next().intValue());
|
||||
assertEquals(12, it.next().intValue());
|
||||
assertEquals(43, it.next().intValue());
|
||||
assertFalse("There should be no more keys.", it.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noConcurrentModificationException()
|
||||
{
|
||||
cache.put(1, "1");
|
||||
cache.put(2, "2");
|
||||
cache.put(3, "3");
|
||||
cache.put(4, "4");
|
||||
|
||||
Iterator<Integer> i = cache.getKeys().iterator();
|
||||
i.next();
|
||||
i.next();
|
||||
|
||||
cache.put(5, "5");
|
||||
|
||||
// Causes a ConcurrentModificationException with a java.util.LinkedHashMap
|
||||
i.next();
|
||||
}
|
||||
}
|
@@ -42,8 +42,8 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
* A 2-level cache that mainains both a transaction-local cache and
|
||||
* wraps a non-transactional (shared) cache.
|
||||
* <p>
|
||||
* It uses the <b>Ehcache</b> <tt>Cache</tt> for it's per-transaction
|
||||
* caches as these provide automatic size limitations, etc.
|
||||
* It uses the <b>shared</b> <tt>SimpleCache</tt> for it's per-transaction
|
||||
* caches as these can provide automatic size limitations, etc.
|
||||
* <p>
|
||||
* Instances of this class <b>do not require a transaction</b>. They will work
|
||||
* directly with the shared cache when no transaction is present. There is
|
||||
|
65
source/java/org/alfresco/repo/cache/lookup/CacheRegionKey.java
vendored
Normal file
65
source/java/org/alfresco/repo/cache/lookup/CacheRegionKey.java
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache.lookup;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Key-wrapper used to separate cache regions, allowing a single cache to be used for different
|
||||
* purposes.<b/>
|
||||
* This class is distinct from the ID key so that ID-based lookups don't class with value-based lookups.
|
||||
*/
|
||||
public class CacheRegionKey implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -213050301938804468L;
|
||||
|
||||
private final String cacheRegion;
|
||||
private final Serializable cacheKey;
|
||||
private final int hashCode;
|
||||
public CacheRegionKey(String cacheRegion, Serializable cacheKey)
|
||||
{
|
||||
this.cacheRegion = cacheRegion;
|
||||
this.cacheKey = cacheKey;
|
||||
this.hashCode = cacheRegion.hashCode() + cacheKey.hashCode();
|
||||
}
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return cacheRegion + "." + cacheKey.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!(obj instanceof CacheRegionKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CacheRegionKey that = (CacheRegionKey) obj;
|
||||
return this.cacheRegion.equals(that.cacheRegion) && this.cacheKey.equals(that.cacheKey);
|
||||
}
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return hashCode;
|
||||
}
|
||||
}
|
66
source/java/org/alfresco/repo/cache/lookup/CacheRegionValueKey.java
vendored
Normal file
66
source/java/org/alfresco/repo/cache/lookup/CacheRegionValueKey.java
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache.lookup;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* Value-key-wrapper used to separate cache regions, allowing a single cache to be used for different
|
||||
* purposes.<b/>
|
||||
* This class is distinct from the region key so that ID-based lookups don't class with value-based lookups.
|
||||
*/
|
||||
public class CacheRegionValueKey implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 5838308035326617927L;
|
||||
|
||||
private final String cacheRegion;
|
||||
private final Serializable cacheValueKey;
|
||||
private final int hashCode;
|
||||
public CacheRegionValueKey(String cacheRegion, Serializable cacheValueKey)
|
||||
{
|
||||
this.cacheRegion = cacheRegion;
|
||||
this.cacheValueKey = cacheValueKey;
|
||||
this.hashCode = cacheRegion.hashCode() + cacheValueKey.hashCode();
|
||||
}
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return cacheRegion + "." + cacheValueKey.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!(obj instanceof CacheRegionValueKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CacheRegionValueKey that = (CacheRegionValueKey) obj;
|
||||
return this.cacheRegion.equals(that.cacheRegion) && this.cacheValueKey.equals(that.cacheValueKey);
|
||||
}
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return hashCode;
|
||||
}
|
||||
}
|
@@ -754,92 +754,4 @@ public class EntityLookupCache<K extends Serializable, V extends Object, VK exte
|
||||
}
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Key-wrapper used to separate cache regions, allowing a single cache to be used for different
|
||||
* purposes.<b/>
|
||||
* This class is distinct from the ID key so that ID-based lookups don't class with value-based lookups.
|
||||
*/
|
||||
private static class CacheRegionKey implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = -213050301938804468L;
|
||||
|
||||
private final String cacheRegion;
|
||||
private final Serializable cacheKey;
|
||||
private final int hashCode;
|
||||
private CacheRegionKey(String cacheRegion, Serializable cacheKey)
|
||||
{
|
||||
this.cacheRegion = cacheRegion;
|
||||
this.cacheKey = cacheKey;
|
||||
this.hashCode = cacheRegion.hashCode() + cacheKey.hashCode();
|
||||
}
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return cacheRegion + "." + cacheKey.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!(obj instanceof CacheRegionKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CacheRegionKey that = (CacheRegionKey) obj;
|
||||
return this.cacheRegion.equals(that.cacheRegion) && this.cacheKey.equals(that.cacheKey);
|
||||
}
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value-key-wrapper used to separate cache regions, allowing a single cache to be used for different
|
||||
* purposes.<b/>
|
||||
* This class is distinct from the region key so that ID-based lookups don't class with value-based lookups.
|
||||
*/
|
||||
private static class CacheRegionValueKey implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 5838308035326617927L;
|
||||
|
||||
private final String cacheRegion;
|
||||
private final Serializable cacheValueKey;
|
||||
private final int hashCode;
|
||||
private CacheRegionValueKey(String cacheRegion, Serializable cacheValueKey)
|
||||
{
|
||||
this.cacheRegion = cacheRegion;
|
||||
this.cacheValueKey = cacheValueKey;
|
||||
this.hashCode = cacheRegion.hashCode() + cacheValueKey.hashCode();
|
||||
}
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return cacheRegion + "." + cacheValueKey.toString();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (!(obj instanceof CacheRegionValueKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
CacheRegionValueKey that = (CacheRegionValueKey) obj;
|
||||
return this.cacheRegion.equals(that.cacheRegion) && this.cacheValueKey.equals(that.cacheValueKey);
|
||||
}
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,362 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.cache.lookup;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.cache.MemoryCache;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO;
|
||||
import org.alfresco.util.EqualsHelper;
|
||||
import org.alfresco.util.Pair;
|
||||
|
||||
/**
|
||||
* A cache for two-way lookups of database entities. These are characterized by having a unique
|
||||
* key (perhaps a database ID) and a separate unique key that identifies the object.
|
||||
* <p>
|
||||
* The keys must have good <code>equals</code> and </code>hashCode</code> implementations and
|
||||
* must respect the case-sensitivity of the use-case.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 3.2
|
||||
*/
|
||||
public class EntityLookupCacheTest extends TestCase implements EntityLookupCallbackDAO<Long, Object, String>
|
||||
{
|
||||
SimpleCache<Long, Object> cache;
|
||||
private EntityLookupCache<Long, Object, String> entityLookupCacheA;
|
||||
private EntityLookupCache<Long, Object, String> entityLookupCacheB;
|
||||
private TreeMap<Long, String> database;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
cache = new MemoryCache<Long, Object>();
|
||||
entityLookupCacheA = new EntityLookupCache<Long, Object, String>(cache, "A", this);
|
||||
entityLookupCacheB = new EntityLookupCache<Long, Object, String>(cache, "B", this);
|
||||
database = new TreeMap<Long, String>();
|
||||
}
|
||||
|
||||
public void testLookupsUsingIncorrectValue() throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
// Keep the "database" empty
|
||||
entityLookupCacheA.getByValue(this);
|
||||
}
|
||||
catch (AssertionFailedError e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testLookupAgainstEmpty() throws Exception
|
||||
{
|
||||
TestValue value = new TestValue("AAA");
|
||||
Pair<Long, Object> entityPair = entityLookupCacheA.getByValue(value);
|
||||
assertNull(entityPair);
|
||||
assertTrue(database.isEmpty());
|
||||
|
||||
// Now do lookup or create
|
||||
entityPair = entityLookupCacheA.getOrCreateByValue(value);
|
||||
assertNotNull("Expected a value to be found", entityPair);
|
||||
Long entityId = entityPair.getFirst();
|
||||
assertTrue("Database ID should have been created", database.containsKey(entityId));
|
||||
assertEquals("Database value incorrect", value.val, database.get(entityId));
|
||||
|
||||
// Do lookup or create again
|
||||
entityPair = entityLookupCacheA.getOrCreateByValue(value);
|
||||
assertNotNull("Expected a value to be found", entityPair);
|
||||
assertEquals("Expected same entity ID", entityId, entityPair.getFirst());
|
||||
|
||||
// Look it up using the value
|
||||
entityPair = entityLookupCacheA.getByValue(value);
|
||||
assertNotNull("Lookup after create should work", entityPair);
|
||||
|
||||
// Look it up using the ID
|
||||
entityPair = entityLookupCacheA.getByKey(entityId);
|
||||
assertNotNull("Lookup by key should work after create", entityPair);
|
||||
assertTrue("Looked-up type incorrect", entityPair.getSecond() instanceof TestValue);
|
||||
assertEquals("Looked-up type value incorrect", value, entityPair.getSecond());
|
||||
}
|
||||
|
||||
public void testLookupAgainstExisting() throws Exception
|
||||
{
|
||||
// Put some values in the "database"
|
||||
createValue(new TestValue("AAA"));
|
||||
createValue(new TestValue("BBB"));
|
||||
createValue(new TestValue("CCC"));
|
||||
|
||||
// Look up by value
|
||||
Pair<Long, Object> entityPair = entityLookupCacheA.getByValue(new TestValue("AAA"));
|
||||
assertNotNull("Expected value to be found", entityPair);
|
||||
assertEquals("ID is incorrect", new Long(1), entityPair.getFirst());
|
||||
|
||||
// Look up by ID
|
||||
entityPair = entityLookupCacheA.getByKey(new Long(2));
|
||||
assertNotNull("Expected value to be found", entityPair);
|
||||
|
||||
// Do lookup or create
|
||||
entityPair = entityLookupCacheA.getByValue(new TestValue("CCC"));
|
||||
assertNotNull("Expected value to be found", entityPair);
|
||||
assertEquals("ID is incorrect", new Long(3), entityPair.getFirst());
|
||||
}
|
||||
|
||||
public void testRegions() throws Exception
|
||||
{
|
||||
TestValue valueAAA = new TestValue("AAA");
|
||||
Pair<Long, Object> entityPairAAA = entityLookupCacheA.getOrCreateByValue(valueAAA);
|
||||
assertNotNull(entityPairAAA);
|
||||
assertEquals("AAA", database.get(entityPairAAA.getFirst()));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
TestValue valueBBB = new TestValue("BBB");
|
||||
Pair<Long, Object> entityPairBBB = entityLookupCacheB.getOrCreateByValue(valueBBB);
|
||||
assertNotNull(entityPairBBB);
|
||||
assertEquals("BBB", database.get(entityPairBBB.getFirst()));
|
||||
assertEquals(4, cache.getKeys().size());
|
||||
|
||||
// Now cross-check against the caches and make sure that the cache
|
||||
entityPairBBB = entityLookupCacheA.getByValue(valueBBB);
|
||||
assertEquals(6, cache.getKeys().size());
|
||||
entityPairBBB = entityLookupCacheB.getByValue(valueAAA);
|
||||
assertEquals(8, cache.getKeys().size());
|
||||
}
|
||||
|
||||
public void testNullLookups() throws Exception
|
||||
{
|
||||
TestValue valueNull = null;
|
||||
Pair<Long, Object> entityPairNull = entityLookupCacheA.getOrCreateByValue(valueNull);
|
||||
assertNotNull(entityPairNull);
|
||||
assertTrue(database.containsKey(entityPairNull.getFirst()));
|
||||
assertNull(database.get(entityPairNull.getFirst()));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
// Look it up again
|
||||
Pair<Long, Object> entityPairCheck = entityLookupCacheA.getOrCreateByValue(valueNull);
|
||||
assertNotNull(entityPairNull);
|
||||
assertTrue(database.containsKey(entityPairNull.getFirst()));
|
||||
assertNull(database.get(entityPairNull.getFirst()));
|
||||
assertEquals(entityPairNull, entityPairCheck);
|
||||
}
|
||||
|
||||
public void testUpdate() throws Exception
|
||||
{
|
||||
TestValue valueOne = new TestValue(getName() + "-ONE");
|
||||
TestValue valueTwo = new TestValue(getName() + "-TWO");
|
||||
Pair<Long, Object> entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne);
|
||||
assertNotNull(entityPairOne);
|
||||
Long id = entityPairOne.getFirst();
|
||||
assertEquals(valueOne.val, database.get(id));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
// Update
|
||||
int updateCount = entityLookupCacheA.updateValue(id, valueTwo);
|
||||
assertEquals("Update count was incorrect.", 1, updateCount);
|
||||
assertEquals(valueTwo.val, database.get(id));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
}
|
||||
|
||||
public void testDeleteByKey() throws Exception
|
||||
{
|
||||
TestValue valueOne = new TestValue(getName() + "-ONE");
|
||||
Pair<Long, Object> entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne);
|
||||
assertNotNull(entityPairOne);
|
||||
Long id = entityPairOne.getFirst();
|
||||
assertEquals(valueOne.val, database.get(id));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
// Delete
|
||||
int deleteCount = entityLookupCacheA.deleteByKey(id);
|
||||
assertEquals("Delete count was incorrect.", 1, deleteCount);
|
||||
assertNull(database.get(id));
|
||||
assertEquals(0, cache.getKeys().size());
|
||||
}
|
||||
|
||||
public void testDeleteByValue() throws Exception
|
||||
{
|
||||
TestValue valueOne = new TestValue(getName() + "-ONE");
|
||||
Pair<Long, Object> entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne);
|
||||
assertNotNull(entityPairOne);
|
||||
Long id = entityPairOne.getFirst();
|
||||
assertEquals(valueOne.val, database.get(id));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
// Delete
|
||||
int deleteCount = entityLookupCacheA.deleteByValue(valueOne);
|
||||
assertEquals("Delete count was incorrect.", 1, deleteCount);
|
||||
assertNull(database.get(id));
|
||||
assertEquals(0, cache.getKeys().size());
|
||||
}
|
||||
|
||||
public void testClear() throws Exception
|
||||
{
|
||||
TestValue valueOne = new TestValue(getName() + "-ONE");
|
||||
Pair<Long, Object> entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne);
|
||||
assertNotNull(entityPairOne);
|
||||
Long id = entityPairOne.getFirst();
|
||||
assertEquals(valueOne.val, database.get(id));
|
||||
assertEquals(2, cache.getKeys().size());
|
||||
|
||||
// Clear it
|
||||
entityLookupCacheA.clear();
|
||||
assertEquals(valueOne.val, database.get(id)); // Must still be in database
|
||||
assertEquals(0, cache.getKeys().size()); // ... but cache must be empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to represent business object
|
||||
*/
|
||||
private static class TestValue
|
||||
{
|
||||
private final String val;
|
||||
private TestValue(String val)
|
||||
{
|
||||
this.val = val;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null || !(obj instanceof TestValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return val.equals( ((TestValue)obj).val );
|
||||
}
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return val.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getValueKey(Object value)
|
||||
{
|
||||
assertNotNull(value);
|
||||
assertTrue(value instanceof TestValue);
|
||||
String dbValue = ((TestValue)value).val;
|
||||
return dbValue;
|
||||
}
|
||||
|
||||
public Pair<Long, Object> findByKey(Long key)
|
||||
{
|
||||
assertNotNull(key);
|
||||
|
||||
String dbValue = database.get(key);
|
||||
if (dbValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Make a value object
|
||||
TestValue value = new TestValue(dbValue);
|
||||
return new Pair<Long, Object>(key, value);
|
||||
}
|
||||
|
||||
public Pair<Long, Object> findByValue(Object value)
|
||||
{
|
||||
assertTrue(value == null || value instanceof TestValue);
|
||||
String dbValue = (value == null) ? null : ((TestValue)value).val;
|
||||
|
||||
for (Map.Entry<Long, String> entry : database.entrySet())
|
||||
{
|
||||
if (EqualsHelper.nullSafeEquals(entry.getValue(), dbValue))
|
||||
{
|
||||
return new Pair<Long, Object>(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate creation of a new database entry
|
||||
*/
|
||||
public Pair<Long, Object> createValue(Object value)
|
||||
{
|
||||
assertTrue(value == null || value instanceof TestValue);
|
||||
String dbValue = (value == null) ? null : ((TestValue)value).val;
|
||||
|
||||
// Get the last key
|
||||
Long lastKey = database.isEmpty() ? null : database.lastKey();
|
||||
Long newKey = null;
|
||||
if (lastKey == null)
|
||||
{
|
||||
newKey = new Long(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
newKey = new Long(lastKey.longValue() + 1);
|
||||
}
|
||||
database.put(newKey, dbValue);
|
||||
return new Pair<Long, Object>(newKey, value);
|
||||
}
|
||||
|
||||
public int updateValue(Long key, Object value)
|
||||
{
|
||||
assertNotNull(key);
|
||||
assertTrue(value == null || value instanceof TestValue);
|
||||
|
||||
// Find it
|
||||
Pair<Long, Object> entityPair = findByKey(key);
|
||||
if (entityPair == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
database.put(key, ((TestValue)value).val);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int deleteByKey(Long key)
|
||||
{
|
||||
assertNotNull(key);
|
||||
|
||||
if (database.containsKey(key))
|
||||
{
|
||||
database.remove(key);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int deleteByValue(Object value)
|
||||
{
|
||||
assertTrue(value == null || value instanceof TestValue);
|
||||
|
||||
// Find it
|
||||
Pair<Long, Object> entityPair = findByValue(value);
|
||||
if (entityPair == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
database.remove(entityPair.getFirst());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user