From 8d90b426dcc0fbfcf935f6e8a7fedcc7c0cf0c89 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Mon, 3 Aug 2009 17:56:43 +0000 Subject: [PATCH] Further improvements to the EntityLookupCache for NULL values - Supports entities that map IDs to potentially-null values - Fix some bugs git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@15555 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../repo/cache/lookup/EntityLookupCache.java | 167 ++++++++++++++---- .../cache/lookup/EntityLookupCacheTest.java | 76 ++++---- 2 files changed, 174 insertions(+), 69 deletions(-) diff --git a/source/java/org/alfresco/repo/cache/lookup/EntityLookupCache.java b/source/java/org/alfresco/repo/cache/lookup/EntityLookupCache.java index 47af7166eb..06a46f4ae2 100644 --- a/source/java/org/alfresco/repo/cache/lookup/EntityLookupCache.java +++ b/source/java/org/alfresco/repo/cache/lookup/EntityLookupCache.java @@ -40,6 +40,14 @@ import org.alfresco.util.ParameterCheck; *

* All keys will be unique to the given cache region, allowing the cache to be shared * between instances of this class. + *

+ * Generics: + *

* * @author Derek Hulley * @since 3.3 @@ -53,19 +61,27 @@ public class EntityLookupCache + * A return value should be small and efficient; don't return a value if this is not possible. + *

* Implementations will often return the value itself, provided that the value is both * serializable and has a good equals and hashCode. + *

+ * Were no adequate key can be generated for the value, then null can be returned. + * In this case, the {@link #findByValue(Object) findByValue} method might not even do a search + * and just return null itself i.e. if it is difficult to look the value up in storage + * then it is probably difficult to generate a cache key from it, too.. In this scenario, the + * cache will be purely for key-based lookups * - * @param value the full value being keyed - * @return Returns the business key representing the entity + * @param value the full value being keyed (never null) + * @return Returns the business key representing the entity, or null + * if an economical key cannot be generated. */ VK1 getValueKey(V1 value); /** * Find an entity for a given key. * - * @param key the key (ID) used to identify the entity + * @param key the key (ID) used to identify the entity (never null) * @return Return the entity or null if no entity is exists for the ID */ Pair findByKey(K1 key); @@ -74,18 +90,46 @@ public class EntityLookupCacheequals and hashCode * methods of the value object should respect case-sensitivity in the same way that this * lookup treats case-sensitivity i.e. if the equals method is case-sensitive - * then this method should look the entity up using a case-sensitive search. Where the - * behaviour is configurable, + * then this method should look the entity up using a case-sensitive search. + *

+ * Since this is a cache backed by some sort of database, null values are allowed by the + * cache. The implementation of this method can throw an exception if null is not + * appropriate for the use-case. + *

+ * If the search is impossible or expensive, this method should just return null. This + * would usually be the case if the {@link #getValueKey(Object) getValueKey} method also returned + * null i.e. if it is difficult to look the value up in storage then it is probably + * difficult to generate a cache key from it, too. * - * @param value the value (business object) used to identify the entity + * @param value the value (business object) used to identify the entity (null allowed). * @return Return the entity or null if no entity matches the given value */ Pair findByValue(V1 value); + /** + * Create an entity using the given values. It is valid to assume that the entity does not exist + * within the current transaction at least. + *

+ * Since persistence mechanisms often allow null values, these can be expected here. The + * implementation must throw an exception if null is not allowed for the specific use-case. + * + * @param value the value (business object) used to identify the entity (null allowed). + * @return Return the newly-created entity ID-value pair + */ Pair createValue(V1 value); } - private static final String NULL_VALUE = "@@NULL_VALUE@@"; + /** + * A valid null value i.e. a value that has been persisted as null. + */ + private static final Serializable VALUE_NULL = "@@VALUE_NULL@@"; + /** + * A value that was not found or persisted. + */ + private static final Serializable VALUE_NOT_FOUND = "@@VALUE_NOT_FOUND@@"; + /** + * The cache region that will be used (see {@link CacheRegionKey}) in all the cache keys + */ private static final String CACHE_REGION_DEFAULT = "DEFAULT"; private final SimpleCache cache; @@ -139,6 +183,10 @@ public class EntityLookupCache getByKey(K key) { + if (key == null) + { + throw new IllegalArgumentException("An entity lookup key may not be null"); + } // Handle missing cache if (cache == null) { @@ -148,26 +196,36 @@ public class EntityLookupCache(key, value); + if (value.equals(VALUE_NOT_FOUND)) + { + // We checked before + return null; + } + else if (value.equals(VALUE_NULL)) + { + return new Pair(key, null); + } + else + { + return new Pair(key, value); + } } // Resolve it Pair entityPair = entityLookup.findByKey(key); if (entityPair == null) { - // Cache nulls - cache.put(cacheKey, NULL_VALUE); + // Cache "not found" + cache.put(cacheKey, VALUE_NOT_FOUND); } else { + value = entityPair.getSecond(); // Cache the value - cache.put(cacheKey, entityPair.getSecond()); + cache.put( + cacheKey, + (value == null ? VALUE_NULL : value)); } // Done return entityPair; @@ -182,33 +240,49 @@ public class EntityLookupCache