| /* |
| * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.internal.identitymaps; |
| |
| import java.io.Serializable; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| |
| /** |
| * <p><b>Purpose</b>: Caches objects, and allows their retrieval by their primary key. |
| * <p><b>Responsibilities</b>: |
| * <ul> |
| * <li> Store CacheKeys containing objects and possibly writeLockValues.</li> |
| * <li> Insert & retrieve objects from the cache.</li> |
| * <li> Allow retrieval and modification of writeLockValue for a cached object.</li> |
| * </ul> |
| * @see CacheKey |
| * @since TOPLink/Java 1.0 |
| */ |
| public abstract class AbstractIdentityMap implements IdentityMap, Serializable, Cloneable { |
| |
| /** The initial or maximum size of the cache depending upon the concrete implementation. */ |
| protected int maxSize; |
| |
| /** PERF: Store the descriptor to allow lastAccessed cache lookup optimization. */ |
| protected transient ClassDescriptor descriptor; |
| |
| /** Is this identity map within an IsolatedClientSession */ |
| protected boolean isIsolated; |
| |
| /** Session that the map is on */ |
| protected AbstractSession session; |
| |
| public AbstractIdentityMap(){ |
| } |
| |
| /** |
| * Instantiate an new IdentityMap with it's maximum size.<p> |
| * <b>NOTE</b>: Subclasses may provide different behavior for maxSize. |
| * @param size is the maximum size to be allocated for the receiver. |
| */ |
| public AbstractIdentityMap(int size, ClassDescriptor descriptor, AbstractSession session, boolean isolated) { |
| this.maxSize = size; |
| this.descriptor = descriptor; |
| this.isIsolated = isolated; |
| this.session = session; |
| } |
| |
| /** |
| * Acquire a deferred lock on the object. |
| * This is used while reading if the object has relationships without indirection. |
| * This first thread will get an active lock. |
| * Other threads will get deferred locks, all threads will wait until all other threads are complete before releasing their locks. |
| */ |
| @Override |
| public CacheKey acquireDeferredLock(Object primaryKey, boolean isCacheCheckComplete) { |
| CacheKey cacheKey = getCacheKey(primaryKey, false); |
| if (cacheKey == null) { |
| // Create and lock a new cacheKey. |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| newCacheKey.acquireDeferredLock(); |
| // PERF: To avoid synchronization, getIfAbsentPut is used. |
| cacheKey = putCacheKeyIfAbsent(newCacheKey); |
| // Return if the newCacheKey was put as it was already locked, otherwise must still lock it. |
| if (cacheKey == null) { |
| return newCacheKey; |
| } else { |
| newCacheKey.releaseDeferredLock(); |
| } |
| } |
| // Acquire a lock on the cache key. |
| cacheKey.acquireDeferredLock(); |
| return cacheKey; |
| } |
| |
| /** |
| * Acquire an active lock on the object. |
| * This is used by reading (when using indirection or no relationships) and by merge. |
| */ |
| @Override |
| public CacheKey acquireLock(Object primaryKey, boolean forMerge, boolean isCacheCheckComplete) { |
| CacheKey cacheKey = getCacheKey(primaryKey, forMerge); |
| if (cacheKey == null) { |
| // Create and lock a new cacheKey. |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| newCacheKey.acquire(forMerge); |
| // PERF: To avoid synchronization, getIfAbsentPut is used. |
| cacheKey = putCacheKeyIfAbsent(newCacheKey); |
| // Return if the newCacheKey was put as it was already locked, otherwise must still lock it. |
| if (cacheKey == null) { |
| return newCacheKey; |
| } else { |
| newCacheKey.release(); |
| } |
| } |
| // Acquire a lock on the cache key. |
| cacheKey.acquire(); |
| return cacheKey; |
| } |
| |
| /** |
| * Acquire an active lock on the object, if not already locked. |
| * This is used by merge for missing existing objects. |
| */ |
| @Override |
| public CacheKey acquireLockNoWait(Object primaryKey, boolean forMerge) { |
| CacheKey cacheKey = getCacheKey(primaryKey, forMerge); |
| if (cacheKey == null) { |
| // Create and lock a new cacheKey. |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| newCacheKey.acquire(forMerge); |
| // PERF: To avoid synchronization, getIfAbsentPut is used. |
| cacheKey = putCacheKeyIfAbsent(newCacheKey); |
| // Return if the newCacheKey was put as already lock, otherwise must still lock. |
| if (cacheKey == null) { |
| return newCacheKey; |
| } else { |
| newCacheKey.release(); |
| } |
| } |
| // Acquire a lock on the cache key. |
| // Return null if already locked. |
| if (!cacheKey.acquireNoWait(forMerge)) { |
| return null; |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Acquire an active lock on the object, if not already locked. |
| * This is used by merge for missing existing objects. |
| */ |
| @Override |
| public CacheKey acquireLockWithWait(Object primaryKey, boolean forMerge, int wait) { |
| CacheKey cacheKey = getCacheKey(primaryKey, forMerge); |
| if (cacheKey == null) { |
| // Create and lock a new cacheKey. |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| newCacheKey.acquire(forMerge); |
| // PERF: To avoid synchronization, getIfAbsentPut is used. |
| cacheKey = putCacheKeyIfAbsent(newCacheKey); |
| // Return if the newCacheKey was put as already lock, otherwise must still lock. |
| if (cacheKey == null) { |
| return newCacheKey; |
| } else { |
| newCacheKey.release(); |
| } |
| } |
| // Acquire a lock on the cache key. |
| // Return null if already locked. |
| if (!cacheKey.acquireWithWait(forMerge, wait)) { |
| return null; |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Acquire a read lock on the object. |
| * This is used by UnitOfWork cloning. |
| * This will allow multiple users to read the same object but prevent writes to the object while the read lock is held. |
| */ |
| @Override |
| public CacheKey acquireReadLockOnCacheKey(Object primaryKey) { |
| CacheKey cacheKey = getCacheKey(primaryKey, false); |
| if (cacheKey == null) { |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| // Lock new cacheKey. |
| newCacheKey.acquireReadLock(); |
| // Create one but not put it in the cache, as we are only reading |
| // and should not be writing to the identitymap. |
| return newCacheKey; |
| } |
| // Acquire a lock on the cache key. |
| // Return null if already locked. |
| cacheKey.acquireReadLock(); |
| return cacheKey; |
| } |
| |
| /** |
| * Acquire a read lock on the object, if not already locked. |
| * This is used by UnitOfWork cloning. |
| * This will allow multiple users to read the same object but prevent writes to the object while the read lock is held. |
| */ |
| @Override |
| public CacheKey acquireReadLockOnCacheKeyNoWait(Object primaryKey) { |
| CacheKey cacheKey = getCacheKey(primaryKey, false); |
| if (cacheKey == null) { |
| CacheKey newCacheKey = createCacheKey(primaryKey, null, null, 0); |
| // Lock new cacheKey. |
| newCacheKey.acquireReadLock(); |
| // Create one but not put it in the cache, as we are only reading |
| // and should not be writing to the identitymap. |
| return newCacheKey; |
| } |
| // Acquire a lock on the cache key. |
| // Return null if already locked. |
| if (!cacheKey.acquireReadLockNoWait()) { |
| return null; |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Add all locked CacheKeys to the map grouped by thread. |
| * Used to print all the locks in the identity map. |
| */ |
| @Override |
| public abstract void collectLocks(HashMap threadList); |
| |
| /** |
| * Clone the map and all of the CacheKeys. |
| * This is used by UnitOfWork commitAndResumeOnFailure to avoid corrupting the cache during a failed commit. |
| */ |
| @Override |
| public Object clone() { |
| try { |
| return super.clone(); |
| } catch (CloneNotSupportedException exception) { |
| throw new InternalError(exception.toString()); |
| } |
| } |
| |
| /** |
| * Return true if an CacheKey with the primary key is in the map. |
| * User API. |
| * @param primaryKey is the primary key for the object to search for. |
| */ |
| @Override |
| public boolean containsKey(Object primaryKey) { |
| return getCacheKeyWithReadLock(primaryKey) != null; |
| } |
| |
| /** |
| * Create the correct type of CacheKey for this map. |
| */ |
| public CacheKey createCacheKey(Object primaryKey, Object object, Object writeLockValue, long readTime) { |
| return new CacheKey(primaryKey, object, writeLockValue, readTime, this.isIsolated); |
| } |
| |
| /** |
| * Allow for the cache to be iterated on. |
| */ |
| @Override |
| public abstract Enumeration elements(); |
| |
| /** |
| * Return the object cached in the identity map or null if it could not be found. |
| * User API. |
| */ |
| @Override |
| public Object get(Object primaryKey) { |
| CacheKey cacheKey = getCacheKeyWithReadLock(primaryKey); |
| if (cacheKey == null) { |
| return null; |
| } |
| return cacheKey.getObject(); |
| } |
| |
| /** |
| * ADVANCED: |
| * Using a list of Entity PK this method will attempt to bulk load the entire list from the cache. |
| * In certain circumstances this can have large performance improvements over loading each item individually. |
| * @param pkList List of Entity PKs to extract from the cache |
| * @param descriptor Descriptor type to be retrieved. |
| * @return Map of Entity PKs associated to the Entities that were retrieved |
| * @throws QueryException |
| */ |
| @Override |
| public Map<Object, Object> getAllFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session){ |
| HashMap<Object, Object> map = new HashMap<>(); |
| CacheKey cachedObject = null; |
| long currentTime = System.currentTimeMillis(); |
| for (Object pk : pkList){ |
| cachedObject = getCacheKey(pk, false); |
| if ((cachedObject != null && cachedObject.getObject() != null && !descriptor.getCacheInvalidationPolicy().isInvalidated(cachedObject, currentTime))){ |
| map.put(pk, cachedObject.getObject()); |
| } |
| } |
| return map; |
| } |
| |
| /** |
| * ADVANCED: |
| * Using a list of Entity PK this method will attempt to bulk load the entire list from the cache. |
| * In certain circumstances this can have large performance improvements over loading each item individually. |
| * @param pkList List of Entity PKs to extract from the cache |
| * @param descriptor Descriptor type to be retrieved. |
| * @return Map of Entity PKs associated to the Entities that were retrieved |
| * @throws QueryException |
| */ |
| @Override |
| public Map<Object, CacheKey> getAllCacheKeysFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session){ |
| HashMap<Object, CacheKey> map = new HashMap<>(); |
| CacheKey cachedObject = null; |
| long currentTime = System.currentTimeMillis(); |
| for (Object pk : pkList){ |
| cachedObject = getCacheKey(pk, false); |
| if ((cachedObject != null && cachedObject.getObject() != null && !descriptor.getCacheInvalidationPolicy().isInvalidated(cachedObject, currentTime))){ |
| map.put(pk, cachedObject); |
| } |
| } |
| return map; |
| } |
| |
| /** |
| * Get the cache key (with object) for the primary key. |
| */ |
| @Override |
| public abstract CacheKey getCacheKey(Object primaryKey, boolean forMerge); |
| |
| /** |
| * Get the cache key (with object) for the primary key. |
| */ |
| @Override |
| public CacheKey getCacheKeyForLock(Object primaryKey) { |
| return getCacheKey(primaryKey, true); |
| } |
| |
| /** |
| * Return the CacheKey (with object) matching the searchKey. |
| * If the CacheKey is missing then put the searchKey in the map. |
| * The searchKey should have already been locked. |
| */ |
| protected abstract CacheKey putCacheKeyIfAbsent(CacheKey cacheKey); |
| |
| /** |
| * Get the cache key (with object) for the primary key with read lock. |
| */ |
| protected CacheKey getCacheKeyWithReadLock(Object primaryKey) { |
| CacheKey key = getCacheKey(primaryKey, false); |
| if (key != null) { |
| key.checkReadLock(); |
| } |
| return key; |
| } |
| |
| /** |
| * Returns the class which should be used as an identity map in a descriptor by default. |
| */ |
| public static Class getDefaultIdentityMapClass() { |
| return ClassConstants.SoftCacheWeakIdentityMap_Class; |
| } |
| |
| /** |
| * @return The maxSize for the IdentityMap (NOTE: some subclasses may use this differently). |
| */ |
| @Override |
| public int getMaxSize() { |
| if (maxSize == -1) { |
| maxSize = 100; |
| } |
| return maxSize; |
| } |
| |
| /** |
| * Return the number of CacheKeys in the IdentityMap. |
| * This may contain weak referenced objects that have been garbage collected. |
| */ |
| @Override |
| public abstract int getSize(); |
| |
| /** |
| * Return the number of actual objects of type myClass in the IdentityMap. |
| * Recurse = true will include subclasses of myClass in the count. |
| */ |
| @Override |
| public abstract int getSize(Class myClass, boolean recurse); |
| |
| /** |
| * Get the wrapper object from the cache key associated with the given primary key, |
| * this is used for EJB2. |
| */ |
| @Override |
| public Object getWrapper(Object primaryKey) { |
| CacheKey cacheKey = getCacheKeyWithReadLock(primaryKey); |
| if (cacheKey == null) { |
| return null; |
| } else { |
| return cacheKey.getWrapper(); |
| } |
| } |
| |
| /** |
| * Get the write lock value from the cache key associated to the primarykey. |
| * User API. |
| */ |
| @Override |
| public Object getWriteLockValue(Object primaryKey) { |
| CacheKey cacheKey = getCacheKeyWithReadLock(primaryKey); |
| if (cacheKey == null) { |
| return null; |
| } else { |
| return cacheKey.getWriteLockValue(); |
| } |
| } |
| |
| /** |
| * Allow for the CacheKeys to be iterated on. |
| */ |
| @Override |
| public abstract Enumeration keys(); |
| |
| /** |
| * Store the object in the cache at its primary key. |
| * This is used by InsertObjectQuery, typically into the UnitOfWork identity map. |
| * Merge and reads do not use put, but acquireLock. |
| * Also an advanced (very) user API. |
| * @param primaryKey is the primary key for the object. |
| * @param object is the domain object to cache. |
| * @param writeLockValue is the current write lock value of object, if null the version is ignored. |
| */ |
| @Override |
| public abstract CacheKey put(Object primaryKey, Object object, Object writeLockValue, long readTime); |
| |
| /** |
| * This method may be called during initialize all identity maps. It allows the identity map |
| * or interceptor the opportunity to release any resources before being thrown away. |
| */ |
| @Override |
| public void release(){ |
| //no-op |
| } |
| |
| /** |
| * Remove the CacheKey with the primaryKey from the map. |
| * This is used by DeleteObjectQuery and merge. |
| * This is also an advanced (very) user API. |
| */ |
| @Override |
| public Object remove(Object primaryKey, Object object) { |
| CacheKey key = getCacheKeyForLock(primaryKey); |
| return remove(key); |
| } |
| |
| /** |
| * Remove the CacheKey from the map. |
| */ |
| @Override |
| public abstract Object remove(CacheKey cacheKey); |
| |
| /** |
| * Set the maximum size for the receiver. |
| * @param size is the new maximum size. |
| */ |
| protected synchronized void setMaxSize(int size) { |
| maxSize = size; |
| } |
| |
| /** |
| * This method will be used to update the max cache size, any objects exceeding the max cache size will |
| * be remove from the cache. Please note that this does not remove the object from the identityMap, except in |
| * the case of the CacheIdentityMap. |
| */ |
| @Override |
| public void updateMaxSize(int maxSize) { |
| setMaxSize(maxSize); |
| } |
| |
| /** |
| * Return the class that this is the map for. |
| */ |
| @Override |
| public ClassDescriptor getDescriptor() { |
| return descriptor; |
| } |
| |
| /** |
| * Return the class that this is the map for. |
| */ |
| @Override |
| public Class getDescriptorClass() { |
| if (descriptor == null) { |
| return null; |
| } |
| return descriptor.getJavaClass(); |
| } |
| |
| /** |
| * Set the descriptor that this is the map for. |
| */ |
| @Override |
| public void setDescriptor(ClassDescriptor descriptor) { |
| this.descriptor = descriptor; |
| } |
| |
| /** |
| * Update the wrapper object in the CacheKey associated with the given primaryKey, |
| * this is used for EJB2. |
| */ |
| @Override |
| public void setWrapper(Object primaryKey, Object wrapper) { |
| CacheKey cacheKey = getCacheKeyForLock(primaryKey); |
| if (cacheKey != null) { |
| cacheKey.setWrapper(wrapper); |
| } |
| } |
| |
| /** |
| * Update the write lock value of the CacheKey associated with the given primaryKey. |
| * This is used by UpdateObjectQuery, and is also an advanced (very) user API. |
| */ |
| @Override |
| public void setWriteLockValue(Object primaryKey, Object writeLockValue) { |
| CacheKey cacheKey = getCacheKeyForLock(primaryKey); |
| if (cacheKey != null) { |
| //lock/release the cache key during the lock value updating |
| cacheKey.acquire(); |
| cacheKey.setWriteLockValue(writeLockValue); |
| cacheKey.release(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return Helper.getShortClassName(getClass()) + "[" + getSize() + "]"; |
| } |
| } |