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:
Samuel Langlois
2013-08-20 17:17:31 +00:00
parent 0a36e2af67
commit ab4ca7177f
1576 changed files with 36419 additions and 8603 deletions

View File

@@ -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()
{

View File

@@ -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();
}

View 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);
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}