mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	103438: Working towards a fix for ACE-3948: BM-0004: Factor out select on alf_content_url table during file creation
    - Complement the 'getOrCreate' EntityCache calls with 'createOrGet'
    - Cleanup of ContentDataDAOImpl code before further changes: tabs, @Override, unused code, etc
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@103622 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			802 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			802 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * 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.io.Serializable;
 | |
| import java.sql.Savepoint;
 | |
| 
 | |
| import org.alfresco.repo.cache.SimpleCache;
 | |
| import org.alfresco.repo.domain.control.ControlDAO;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper;
 | |
| import org.alfresco.util.Pair;
 | |
| import org.springframework.dao.ConcurrencyFailureException;
 | |
| import org.springframework.extensions.surf.util.ParameterCheck;
 | |
| 
 | |
| /**
 | |
|  * 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.  If no cache
 | |
|  * is given, then all calls are passed through to the backing DAO.
 | |
|  * <p>
 | |
|  * The keys must have good <code>equals</code> and </code>hashCode</code> implementations and
 | |
|  * must respect the case-sensitivity of the use-case.
 | |
|  * <p>
 | |
|  * All keys will be unique to the given cache region, allowing the cache to be shared
 | |
|  * between instances of this class.
 | |
|  * <p>
 | |
|  * Generics:
 | |
|  * <ul>
 | |
|  *   <li>K:  The database unique identifier.</li>
 | |
|  *   <li>V:  The value stored against K.</li>
 | |
|  *   <li>VK: The a value-derived key that will be used as a cache key when caching K for lookups by V.
 | |
|  *           This can be the value itself if it is itself a good key.</li>
 | |
|  * </ul>
 | |
|  * 
 | |
|  * @author Derek Hulley
 | |
|  * @since 3.2
 | |
|  */
 | |
| public class EntityLookupCache<K extends Serializable, V extends Object, VK extends Serializable>
 | |
| {
 | |
|     /**
 | |
|      * Interface to support lookups of the entities using keys and values.
 | |
|      */
 | |
|     public static interface EntityLookupCallbackDAO<K1 extends Serializable, V1 extends Object, VK1 extends Serializable>
 | |
|     {
 | |
|         /**
 | |
|          * Resolve the given value into a unique value key that can be used to find the entity's ID.
 | |
|          * A return value should be small and efficient; don't return a value if this is not possible.
 | |
|          * <p/>
 | |
|          * Implementations will often return the value itself, provided that the value is both
 | |
|          * serializable and has a good <code>equals</code> and <code>hashCode</code>.
 | |
|          * <p/>
 | |
|          * Were no adequate key can be generated for the value, then <tt>null</tt> can be returned.
 | |
|          * In this case, the {@link #findByValue(Object) findByValue} method might not even do a search
 | |
|          * and just return <tt>null</tt> 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 (never <tt>null</tt>)
 | |
|          * @return              Returns the business key representing the entity, or <tt>null</tt>
 | |
|          *                      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 (never <tt>null</tt>)
 | |
|          * @return              Return the entity or <tt>null</tt> if no entity is exists for the ID
 | |
|          */
 | |
|         Pair<K1, V1> findByKey(K1 key);
 | |
|         
 | |
|         /**
 | |
|          * Find and entity using the given value key.  The <code>equals</code> and <code>hashCode</code>
 | |
|          * methods of the value object should respect case-sensitivity in the same way that this
 | |
|          * lookup treats case-sensitivity i.e. if the <code>equals</code> method is <b>case-sensitive</b>
 | |
|          * then this method should look the entity up using a <b>case-sensitive</b> search.
 | |
|          * <p/>
 | |
|          * Since this is a cache backed by some sort of database, <tt>null</tt> values are allowed by the
 | |
|          * cache.  The implementation of this method can throw an exception if <tt>null</tt> is not
 | |
|          * appropriate for the use-case.
 | |
|          * <p/>
 | |
|          * If the search is impossible or expensive, this method should just return <tt>null</tt>.  This
 | |
|          * would usually be the case if the {@link #getValueKey(Object) getValueKey} method also returned
 | |
|          * <tt>null</tt> 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 (<tt>null</tt> allowed).
 | |
|          * @return              Return the entity or <tt>null</tt> if no entity matches the given value
 | |
|          */
 | |
|         Pair<K1, V1> 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.
 | |
|          * <p/>
 | |
|          * Since persistence mechanisms often allow <tt>null</tt> values, these can be expected here.  The
 | |
|          * implementation  must throw an exception if <tt>null</tt> is not allowed for the specific use-case.
 | |
|          * 
 | |
|          * @param value         the value (business object) used to identify the entity (<tt>null</tt> allowed).
 | |
|          * @return              Return the newly-created entity ID-value pair
 | |
|          */
 | |
|         Pair<K1, V1> createValue(V1 value);
 | |
|         
 | |
|         /**
 | |
|          * Update the entity identified by the given key.
 | |
|          * <p/>
 | |
|          * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|          * or not.
 | |
|          * 
 | |
|          * @param key           the existing key (ID) used to identify the entity (never <tt>null</tt>)
 | |
|          * @param value         the new value
 | |
|          * @return              Returns the row update count.
 | |
|          * @throws UnsupportedOperationException if entity updates are not supported
 | |
|          */
 | |
|         int updateValue(K1 key, V1 value);
 | |
|         
 | |
|         /**
 | |
|          * Delete an entity for the given key.
 | |
|          * <p/>
 | |
|          * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|          * or not.
 | |
|          *  
 | |
|          * @param key           the key (ID) used to identify the entity (never <tt>null</tt>)
 | |
|          * @return              Returns the row deletion count.
 | |
|          * @throws UnsupportedOperationException if entity deletion is not supported
 | |
|          */
 | |
|         int deleteByKey(K1 key);
 | |
|         
 | |
|         /**
 | |
|          * Delete an entity for the given value.
 | |
|          * <p/>
 | |
|          * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|          * or not.
 | |
|          * 
 | |
|          * @param value         the value (business object) used to identify the enitity (<tt>null</tt> allowed)
 | |
|          * @return              Returns the row deletion count.
 | |
|          * @throws UnsupportedOperationException if entity deletion is not supported
 | |
|          */
 | |
|         int deleteByValue(V1 value);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Adaptor for implementations that support immutable entities.  The update and delete operations
 | |
|      * throw {@link UnsupportedOperationException}.
 | |
|      * 
 | |
|      * @author Derek Hulley
 | |
|      * @since 3.2
 | |
|      */
 | |
|     public static abstract class EntityLookupCallbackDAOAdaptor<K2 extends Serializable, V2 extends Object, VK2 extends Serializable>
 | |
|             implements EntityLookupCallbackDAO<K2, V2, VK2>
 | |
|     {
 | |
|         /**
 | |
|          * This implementation never finds a value and is backed by {@link #getValueKey(Object)} returning nothing.
 | |
|          * 
 | |
|          * @return          Returns <tt>null</tt> always
 | |
|          */
 | |
|         public Pair<K2, V2> findByValue(V2 value)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * This implementation does not find by value and is backed by {@link #findByValue(Object)} returning nothing.
 | |
|          * 
 | |
|          * @return          Returns <tt>null</tt> always
 | |
|          */
 | |
|         public VK2 getValueKey(V2 value)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Disallows the operation.
 | |
|          * 
 | |
|          * @throws UnsupportedOperationException        always
 | |
|          */
 | |
|         public int updateValue(K2 key, V2 value)
 | |
|         {
 | |
|             throw new UnsupportedOperationException();
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Disallows the operation.
 | |
|          * 
 | |
|          * @throws UnsupportedOperationException        always
 | |
|          */
 | |
|         public int deleteByKey(K2 key)
 | |
|         {
 | |
|             throw new UnsupportedOperationException("Entity deletion by key is not supported");
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Disallows the operation.
 | |
|          * 
 | |
|          * @throws UnsupportedOperationException        always
 | |
|          */
 | |
|         public int deleteByValue(V2 value)
 | |
|         {
 | |
|             throw new UnsupportedOperationException("Entity deletion by value is not supported");
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * A valid <code>null</code> value i.e. a value that has been <u>persisted</u> 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<Serializable, Object> cache;
 | |
|     private final EntityLookupCallbackDAO<K, V, VK> entityLookup;
 | |
|     private final String cacheRegion;
 | |
| 
 | |
|     /**
 | |
|      * Construct the lookup cache <b>without any cache</b>.  All calls are passed directly to the
 | |
|      * underlying DAO entity lookup.
 | |
|      * 
 | |
|      * @param entityLookup          the instance that is able to find and persist entities
 | |
|      */
 | |
|     public EntityLookupCache(EntityLookupCallbackDAO<K, V, VK> entityLookup)
 | |
|     {
 | |
|         this(null, CACHE_REGION_DEFAULT, entityLookup);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Construct the lookup cache, using the {@link #CACHE_REGION_DEFAULT default cache region}.
 | |
|      * 
 | |
|      * @param cache                 the cache that will back the two-way lookups
 | |
|      * @param entityLookup          the instance that is able to find and persist entities
 | |
|      */
 | |
|     @SuppressWarnings("rawtypes")
 | |
|     public EntityLookupCache(SimpleCache cache, EntityLookupCallbackDAO<K, V, VK> entityLookup)
 | |
|     {
 | |
|         this(cache, CACHE_REGION_DEFAULT, entityLookup);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Construct the lookup cache, using the given cache region.
 | |
|      * <p>
 | |
|      * All keys will be unique to the given cache region, allowing the cache to be shared
 | |
|      * between instances of this class.
 | |
|      * 
 | |
|      * @param cache                 the cache that will back the two-way lookups; <tt>null</tt> to have no backing
 | |
|      *                              in a cache.
 | |
|      * @param cacheRegion           the region within the cache to use.
 | |
|      * @param entityLookup          the instance that is able to find and persist entities
 | |
|      */
 | |
|     @SuppressWarnings({ "rawtypes", "unchecked" })
 | |
|     public EntityLookupCache(SimpleCache cache, String cacheRegion, EntityLookupCallbackDAO<K, V, VK> entityLookup)
 | |
|     {
 | |
|         ParameterCheck.mandatory("cacheRegion", cacheRegion);
 | |
|         ParameterCheck.mandatory("entityLookup", entityLookup);
 | |
|         this.cache = cache;
 | |
|         this.cacheRegion = cacheRegion;
 | |
|         this.entityLookup = entityLookup;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Find the entity associated with the given key.
 | |
|      * The {@link EntityLookupCallbackDAO#findByKey(Serializable) entity callback} will be used if necessary.
 | |
|      * <p/>
 | |
|      * It is up to the client code to decide if a <tt>null</tt> return value indicates a concurrency violation
 | |
|      * or not; the former would normally result in a concurrency-related exception such as
 | |
|      * {@link ConcurrencyFailureException}.
 | |
|      * 
 | |
|      * @param key                   The entity key, which may be valid or invalid (<tt>null</tt> not allowed)
 | |
|      * @return                      Returns the key-value pair or <tt>null</tt> if the key doesn't reference an entity
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Pair<K, V> getByKey(K key)
 | |
|     {
 | |
|         if (key == null)
 | |
|         {
 | |
|             throw new IllegalArgumentException("An entity lookup key may not be null");
 | |
|         }
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return entityLookup.findByKey(key);
 | |
|         }
 | |
|         
 | |
|         CacheRegionKey keyCacheKey = new CacheRegionKey(cacheRegion, key);
 | |
|         // Look in the cache
 | |
|         V value = (V) cache.get(keyCacheKey);
 | |
|         if (value != null)
 | |
|         {
 | |
|             if (value.equals(VALUE_NOT_FOUND))
 | |
|             {
 | |
|                 // We checked before
 | |
|                 return null;
 | |
|             }
 | |
|             else if (value.equals(VALUE_NULL))
 | |
|             {
 | |
|                 return new Pair<K, V>(key, null);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return new Pair<K, V>(key, value);
 | |
|             }
 | |
|         }
 | |
|         // Resolve it
 | |
|         Pair<K, V> entityPair = entityLookup.findByKey(key);
 | |
|         if (entityPair == null)
 | |
|         {
 | |
|             // Cache "not found"
 | |
|             cache.put(keyCacheKey, VALUE_NOT_FOUND);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             value = entityPair.getSecond();
 | |
|             // Get the value key
 | |
|             VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|             // Check if the value has a good key
 | |
|             if (valueKey != null)
 | |
|             {
 | |
|                 CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|                 // The key is good, so we can cache the value
 | |
|                 cache.put(valueCacheKey, key);
 | |
|             }
 | |
|             cache.put(
 | |
|                     keyCacheKey,
 | |
|                     (value == null ? VALUE_NULL : value));
 | |
|         }
 | |
|         // Done
 | |
|         return entityPair;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Find the entity associated with the given value.
 | |
|      * The {@link EntityLookupCallbackDAO#findByValue(Object) entity callback} will be used if no entry exists in the cache.
 | |
|      * <p/>
 | |
|      * It is up to the client code to decide if a <tt>null</tt> return value indicates a concurrency violation
 | |
|      * or not; the former would normally result in a concurrency-related exception such as
 | |
|      * {@link ConcurrencyFailureException}.
 | |
|      * 
 | |
|      * @param value                 The entity value, which may be valid or invalid (<tt>null</tt> is allowed)
 | |
|      * @return                      Returns the key-value pair or <tt>null</tt> if the value doesn't reference an entity
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Pair<K, V> getByValue(V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return entityLookup.findByValue(value);
 | |
|         }
 | |
|         
 | |
|         // Get the value key.
 | |
|         // The cast to (VK) is counter-intuitive, but works because they're all just Serializable
 | |
|         // It's nasty, but hidden from the cache client code.
 | |
|         VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|         // Check if the value has a good key
 | |
|         if (valueKey == null)
 | |
|         {
 | |
|             return entityLookup.findByValue(value);
 | |
|         }
 | |
|         
 | |
|         // Look in the cache
 | |
|         CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|         K key = (K) cache.get(valueCacheKey);
 | |
|         // Check if we have looked this up already
 | |
|         if (key != null)
 | |
|         {
 | |
|             // We checked before and ...
 | |
|             if (key.equals(VALUE_NOT_FOUND))
 | |
|             {
 | |
|                 // ... it didn't exist
 | |
|                 return null;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // ... it did exist
 | |
|                 return getByKey(key);
 | |
|             }
 | |
|         }
 | |
|         // Resolve it
 | |
|         Pair<K, V> entityPair = entityLookup.findByValue(value);
 | |
|         if (entityPair == null)
 | |
|         {
 | |
|             // Cache "not found"
 | |
|             cache.put(valueCacheKey, VALUE_NOT_FOUND);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             key = entityPair.getFirst();
 | |
|             // Cache the key
 | |
|             cache.put(valueCacheKey, key);
 | |
|             cache.put(
 | |
|                     new CacheRegionKey(cacheRegion, key),
 | |
|                     (entityPair.getSecond() == null ? VALUE_NULL : entityPair.getSecond()));
 | |
|         }
 | |
|         // Done
 | |
|         return entityPair;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Attempt to create the entity and, failing that, look it up.<br/>
 | |
|      * This method takes the opposite approach to {@link #getOrCreateByValue(Object)}, which assumes the entity's
 | |
|      * existence: in this case the entity is assumed to NOT exist.
 | |
|      * The {@link EntityLookupCallbackDAO#createValue(Object)} and {@link EntityLookupCallbackDAO#findByValue(Object)}
 | |
|      * will be used if necessary.<br/>
 | |
|      * <p/>
 | |
|      * Use this method when the data involved is seldom reused.
 | |
|      * 
 | |
|      * @param value                 The entity value (<tt>null</tt> is allowed)
 | |
|      * @param controlDAO            an essential DAO required in order to ensure a transactionally-safe attempt at data creation
 | |
|      * @return                      Returns the key-value pair (new or existing and never <tt>null</tt>)
 | |
|      */
 | |
|     public Pair<K, V> createOrGetByValue(V value, ControlDAO controlDAO)
 | |
|     {
 | |
|         if (controlDAO == null)
 | |
|         {
 | |
|             throw new IllegalArgumentException("The ControlDAO is required in order to perform a safe attempted insert.");
 | |
|         }
 | |
|         Savepoint savepoint = controlDAO.createSavepoint("EntityLookupCache.createOrGetByValue");
 | |
|         try
 | |
|         {
 | |
|             Pair<K, V> entityPair = entityLookup.createValue(value);
 | |
|             controlDAO.releaseSavepoint(savepoint);
 | |
|             // Cache it
 | |
|             if (cache != null)
 | |
|             {
 | |
|                 cache.put(
 | |
|                         new CacheRegionKey(cacheRegion, entityPair.getFirst()),
 | |
|                         (entityPair.getSecond() == null ? VALUE_NULL : entityPair.getSecond()));
 | |
|             }
 | |
|             // It's been created and cached
 | |
|             return entityPair;
 | |
|         }
 | |
|         catch (Exception e)
 | |
|         {
 | |
|             controlDAO.rollbackToSavepoint(savepoint);
 | |
|             // Fall through to the usual way, which should find it if the failure cause was a duplicate key
 | |
|             return getOrCreateByValue(value);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Find the entity associated with the given value and create it if it doesn't exist.
 | |
|      * The {@link EntityLookupCallbackDAO#findByValue(Object)} and {@link EntityLookupCallbackDAO#createValue(Object)}
 | |
|      * will be used if necessary.
 | |
|      * 
 | |
|      * @param value                 The entity value (<tt>null</tt> is allowed)
 | |
|      * @return                      Returns the key-value pair (new or existing and never <tt>null</tt>)
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Pair<K, V> getOrCreateByValue(V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             Pair<K, V> entityPair = entityLookup.findByValue(value);
 | |
|             if (entityPair == null)
 | |
|             {
 | |
|                 entityPair = entityLookup.createValue(value);
 | |
|             }
 | |
|             return entityPair;
 | |
|         }
 | |
|         
 | |
|         // Get the value key
 | |
|         // The cast to (VK) is counter-intuitive, but works because they're all just Serializable.
 | |
|         // It's nasty, but hidden from the cache client code.
 | |
|         VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|         // Check if the value has a good key
 | |
|         if (valueKey == null)
 | |
|         {
 | |
|             Pair<K, V> entityPair = entityLookup.findByValue(value);
 | |
|             if (entityPair == null)
 | |
|             {
 | |
|                 entityPair = entityLookup.createValue(value);
 | |
|                 // Cache the value
 | |
|                 cache.put(
 | |
|                         new CacheRegionKey(cacheRegion, entityPair.getFirst()),
 | |
|                         (entityPair.getSecond() == null ? VALUE_NULL : entityPair.getSecond()));
 | |
|             }
 | |
|             return entityPair;
 | |
|         }
 | |
|         
 | |
|         // Look in the cache
 | |
|         CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|         K key = (K) cache.get(valueCacheKey);
 | |
|         // Check if the value is already mapped to a key
 | |
|         if (key != null && !key.equals(VALUE_NOT_FOUND))
 | |
|         {
 | |
|             return getByKey(key);
 | |
|         }
 | |
|         // Resolve it
 | |
|         Pair<K, V> entityPair = entityLookup.findByValue(value);
 | |
|         if (entityPair == null)
 | |
|         {
 | |
|             // Create it
 | |
|             entityPair = entityLookup.createValue(value);
 | |
|         }
 | |
|         key = entityPair.getFirst();
 | |
|         // Cache the key and value
 | |
|         cache.put(valueCacheKey, key);
 | |
|         cache.put(
 | |
|                 new CacheRegionKey(cacheRegion, key),
 | |
|                 (value == null ? VALUE_NULL : value));
 | |
|         // Done
 | |
|         return entityPair;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Update the entity associated with the given key.
 | |
|      * The {@link EntityLookupCallbackDAO#updateValue(Serializable, Object)} callback
 | |
|      * will be used if necessary.
 | |
|      * <p/>
 | |
|      * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|      * or not; usually the former will generate {@link ConcurrencyFailureException} or something recognised
 | |
|      * by the {@link RetryingTransactionHelper#RETRY_EXCEPTIONS RetryingTransactionHelper}.
 | |
|      * 
 | |
|      * @param key                   The entity key, which may be valid or invalid (<tt>null</tt> not allowed)
 | |
|      * @param value                 The new entity value (may be <tt>null</tt>)
 | |
|      * @return                      Returns the row update count.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public int updateValue(K key, V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return entityLookup.updateValue(key, value);
 | |
|         }
 | |
|         
 | |
|         // Remove entries for the key (bidirectional removal removes the old value as well)
 | |
|         // but leave the key as it will get updated
 | |
|         removeByKey(key, false);
 | |
|         
 | |
|         // Do the update
 | |
|         int updateCount = entityLookup.updateValue(key, value);
 | |
|         if (updateCount == 0)
 | |
|         {
 | |
|             // Nothing was done
 | |
|             return updateCount;
 | |
|         }
 | |
|         
 | |
|         // Get the value key.
 | |
|         VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|         // Check if the value has a good key
 | |
|         if (valueKey != null)
 | |
|         {
 | |
|             // There is a good value key, cache by value
 | |
|             CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|             cache.put(valueCacheKey, key);
 | |
|         }
 | |
|         // Cache by key
 | |
|         cache.put(
 | |
|                 new CacheRegionKey(cacheRegion, key),
 | |
|                 (value == null ? VALUE_NULL : value));
 | |
|         // Done
 | |
|         return updateCount;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Get the key for a given value key (note: not 'value' but 'value key').
 | |
|      * 
 | |
|      * @param valueKey                 The entity value key, which must be valid (<tt>null</tt> not allowed)
 | |
|      * @return                      The entity key (may be <tt>null</tt>)
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public K getKey(VK valueKey)
 | |
|     {
 | |
|         // There is a good value key, cache by value
 | |
|         CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|         K key = (K) cache.get(valueCacheKey);
 | |
|         // Check if we have looked this up already
 | |
|         if (key != null && key.equals(VALUE_NOT_FOUND))
 | |
|         {
 | |
|             key = null;
 | |
|         }
 | |
|         return key;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Get the value for a given key
 | |
|      * 
 | |
|      * @param key                   The entity key, which may be valid or invalid (<tt>null</tt> not allowed)
 | |
|      * @return                      The entity value (may be <tt>null</tt>)
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public V getValue(K key)
 | |
|     {
 | |
|         CacheRegionKey keyCacheKey = new CacheRegionKey(cacheRegion, key);
 | |
|         // Look in the cache
 | |
|         V value = (V) cache.get(keyCacheKey);
 | |
|         if (value == null)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         else if (value.equals(VALUE_NOT_FOUND))
 | |
|         {
 | |
|             // We checked before
 | |
|             return null;
 | |
|         }
 | |
|         else if (value.equals(VALUE_NULL))
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return value;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Update the cache's value
 | |
|      * 
 | |
|      * @param key                   The entity key, which may be valid or invalid (<tt>null</tt> not allowed)
 | |
|      * @param value                 The new entity value (may be <tt>null</tt>)
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public void setValue(K key, V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Remove entries for the key (bidirectional removal removes the old value as well)
 | |
|         // but leave the key as it will get updated
 | |
|         removeByKey(key, false);
 | |
|         
 | |
|         // Get the value key.
 | |
|         VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|         // Check if the value has a good key
 | |
|         if (valueKey != null)
 | |
|         {
 | |
|             // There is a good value key, cache by value
 | |
|             CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|             cache.put(valueCacheKey, key);
 | |
|         }
 | |
|         // Cache by key
 | |
|         cache.put(
 | |
|                 new CacheRegionKey(cacheRegion, key),
 | |
|                 (value == null ? VALUE_NULL : value));
 | |
|         // Done
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Delete the entity associated with the given key.
 | |
|      * The {@link EntityLookupCallbackDAO#deleteByKey(Serializable)} callback will be used if necessary.
 | |
|      * <p/>
 | |
|      * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|      * or not; usually the former will generate {@link ConcurrencyFailureException} or something recognised
 | |
|      * by the {@link RetryingTransactionHelper#RETRY_EXCEPTIONS RetryingTransactionHelper}.
 | |
|      * 
 | |
|      * @param key                   the entity key, which may be valid or invalid (<tt>null</tt> not allowed)
 | |
|      * @return                      Returns the row deletion count
 | |
|      */
 | |
|     public int deleteByKey(K key)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return entityLookup.deleteByKey(key);
 | |
|         }
 | |
|         
 | |
|         // Remove entries for the key (bidirectional removal removes the old value as well)
 | |
|         removeByKey(key);
 | |
|         
 | |
|         // Do the delete
 | |
|         return entityLookup.deleteByKey(key);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Delete the entity having the given value..
 | |
|      * The {@link EntityLookupCallbackDAO#deleteByValue(Object)} callback will be used if necessary.
 | |
|      * <p/>
 | |
|      * It is up to the client code to decide if a <tt>0</tt> return value indicates a concurrency violation
 | |
|      * or not; usually the former will generate {@link ConcurrencyFailureException} or something recognised
 | |
|      * by the {@link RetryingTransactionHelper#RETRY_EXCEPTIONS RetryingTransactionHelper}.
 | |
|      * 
 | |
|      * @param value                   the entity value, which may be valid or invalid (<tt>null</tt> allowed)
 | |
|      * @return                      Returns the row deletion count
 | |
|      */
 | |
|     public int deleteByValue(V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return entityLookup.deleteByValue(value);
 | |
|         }
 | |
|         
 | |
|         // Remove entries for the value
 | |
|         removeByValue(value);
 | |
|         
 | |
|         // Do the delete
 | |
|         return entityLookup.deleteByValue(value);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Remove all cache values associated with the given key.
 | |
|      */
 | |
|     public void removeByKey(K key)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         removeByKey(key, true);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Remove all cache values associated with the given key.
 | |
|      * 
 | |
|      * @param removeKey             <tt>true</tt> to remove the given key's entry
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     private void removeByKey(K key, boolean removeKey)
 | |
|     {
 | |
|         CacheRegionKey keyCacheKey = new CacheRegionKey(cacheRegion, key);
 | |
|         V value = (V) cache.get(keyCacheKey);
 | |
|         if (value != null && !value.equals(VALUE_NOT_FOUND))
 | |
|         {
 | |
|             // Get the value key and remove it
 | |
|             VK valueKey = entityLookup.getValueKey(value);
 | |
|             if (valueKey != null)
 | |
|             {
 | |
|                 CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|                 cache.remove(valueCacheKey);
 | |
|             }
 | |
|         }
 | |
|         if (removeKey)
 | |
|         {
 | |
|             cache.remove(keyCacheKey);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Remove all cache values associated with the given value
 | |
|      * 
 | |
|      * @param value                 The entity value (<tt>null</tt> is allowed)
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public void removeByValue(V value)
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Get the value key
 | |
|         VK valueKey = (value == null) ? (VK)VALUE_NULL : entityLookup.getValueKey(value);
 | |
|         if (valueKey == null)
 | |
|         {
 | |
|             // No key generated for the value.  There is nothing that can be done.
 | |
|             return;
 | |
|         }
 | |
|         // Look in the cache
 | |
|         CacheRegionValueKey valueCacheKey = new CacheRegionValueKey(cacheRegion, valueKey);
 | |
|         K key = (K) cache.get(valueCacheKey);
 | |
|         // Check if the value is already mapped to a key
 | |
|         if (key != null && !key.equals(VALUE_NOT_FOUND))
 | |
|         {
 | |
|             CacheRegionKey keyCacheKey = new CacheRegionKey(cacheRegion, key);
 | |
|             cache.remove(keyCacheKey);
 | |
|         }
 | |
|         cache.remove(valueCacheKey);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Cache-only operation: Remove all cache entries
 | |
|      * <p/>
 | |
|      * <b>NOTE:</b> This operation removes ALL entries for ALL cache regions.
 | |
|      */
 | |
|     public void clear()
 | |
|     {
 | |
|         // Handle missing cache
 | |
|         if (cache == null)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         cache.clear();
 | |
|     }
 | |
| }
 |