blob: 6c5ac35d586c04a5ea179ad2b74565398e8d681c [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 &amp; 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;
protected 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.
*/
protected 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
*/
@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
*/
@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() + "]";
}
}