blob: 8e7c76b105a18f915a679c09210420a7620b2c96 [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 org.eclipse.persistence.exceptions.ConcurrencyException;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Record;
/**
* <p><b>Purpose</b>: Container class for storing objects in an IdentityMap.
* <p><b>Responsibilities</b>:<ul>
* <li> Hold key and object.
* <li> Maintain and update the current writeLockValue.
* </ul>
* @since TOPLink/Java 1.0
*/
public class CacheKey extends ConcurrencyManager implements Cloneable {
//These constants are used in extended cache logging to compare cache item creation thread and thread which picking item from the cache
public final long CREATION_THREAD_ID = Thread.currentThread().getId();
public final String CREATION_THREAD_NAME = String.copyValueOf(Thread.currentThread().getName().toCharArray());
public final long CREATION_THREAD_HASHCODE = Thread.currentThread().hashCode();
/** The key holds the vector of primary key values for the object. */
protected Object key;
protected Object object;
//used to store a reference to the map this cachekey is in in cases where the
//cache key is to be removed, prevents us from having to track down the owning
//map
protected IdentityMap mapOwner;
/** The writeLock value is being held as an object so that it might contain a number or timestamp. */
protected Object writeLockValue;
/** The cached wrapper for the object, used in EJB. */
protected Object wrapper;
/** This is used for Document Preservation to cache the record that this object was built from */
protected Record record;
/** This attribute is the system time in milli seconds that the object was last refreshed on */
//CR #4365
// CR #2698903 - fix for the previous fix. No longer using millis.
protected long lastUpdatedQueryId;
/** Invalidation State can be used to indicate whether this cache key is considered valid */
protected int invalidationState = CHECK_INVALIDATION_POLICY;
/** The following constants are used for the invalidationState variable */
public static final int CHECK_INVALIDATION_POLICY = 0;
public static final int CACHE_KEY_INVALID = -1;
public static final int MAX_WAIT_TRIES = 10000;
/** The read time stores the millisecond value of the last time the object help by
this cache key was confirmed as up to date. */
protected long readTime = 0;
/**
* Stores if this CacheKey instance is a wrapper for the underlying CacheKey. CacheKey wrappers
* may be used with cache interceptors.
*/
protected boolean isWrapper = false;
/**
* Stores retrieved FK values for relationships that are not stored in the Entity
*/
protected AbstractRecord protectedForeignKeys;
/**
* Set to true if this CacheKey comes from an IsolatedClientSession, or DatabaseSessionImpl.
*/
protected boolean isIsolated;
/**
* The ID of the database transaction that last wrote the object.
* This is used for database change notification.
*/
protected Object transactionId;
/**
* Internal:
* Only used by subclasses that may want to wrap the cache key. Could be replaced
* by switching to an interface.
*/
protected CacheKey(){
}
public CacheKey(Object primaryKey) {
this.key = primaryKey;
}
public CacheKey(Object primaryKey, Object object, Object lockValue) {
this.key = primaryKey;
this.writeLockValue = lockValue;
//bug4649617 use setter instead of this.object = object to avoid hard reference on object in subclasses
if (object != null) {
setObject(object);
}
}
public CacheKey(Object primaryKey, Object object, Object lockValue, long readTime, boolean isIsolated) {
this.key = primaryKey;
this.writeLockValue = lockValue;
//bug4649617 use setter instead of this.object = object to avoid hard reference on object in subclasses
if (object != null) {
setObject(object);
}
this.readTime = readTime;
this.isIsolated = isIsolated;
}
/**
* Acquire the lock on the cache key object.
*/
@Override
public void acquire() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquire(false);
}
/**
* Acquire the lock on the cache key object. For the merge process
* called with true from the merge process, if true then the refresh will not refresh the object
*/
@Override
public void acquire(boolean forMerge) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquire(forMerge);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
*/
@Override
public boolean acquireNoWait() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireNoWait(false);
}
/**
* Acquire the lock on the cache key object. Only acquire a lock if the cache key's
* active thread is not set.
* Added for Bug 5840635
*/
public boolean acquireIfUnownedNoWait() {
if (this.isIsolated) {
if (this.depth.get() > 0) {
return false;
}
this.depth.incrementAndGet();
return true;
}
return super.acquireIfUnownedNoWait(false);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
@Override
public boolean acquireNoWait(boolean forMerge) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireNoWait(forMerge);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
@Override
public boolean acquireWithWait(boolean forMerge, int wait) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireWithWait(forMerge, wait);
}
/**
* Acquire the deferred lock.
*/
@Override
public void acquireDeferredLock() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquireDeferredLock();
}
public void acquireLock(ObjectBuildingQuery query){
// PERF: Only use deferred locking if required.
// CR#3876308 If joining is used, deferred locks are still required.
if (query.requiresDeferredLocks()) {
this.acquireDeferredLock();
int counter = 0;
while ((this.object == null) && (counter < 1000)) {
if (this.getActiveThread() == Thread.currentThread()) {
break;
}
//must release lock here to prevent acquiring multiple deferred locks but only
//releasing one at the end of the build object call.
//bug 5156075
this.releaseDeferredLock();
//sleep and try again if we are not the owner of the lock for CR 2317
// prevents us from modifying a cache key that another thread has locked.
try {
Thread.sleep(10);
} catch (InterruptedException exception) {
}
this.acquireDeferredLock();
counter++;
}
if (counter == 1000) {
throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(this.getActiveThread(), Thread.currentThread());
}
} else {
this.acquire();
}
}
/**
* Check the read lock on the cache key object.
* This can be called to ensure the cache key has a valid built object.
* It does not hold a lock, so the object could be refreshed afterwards.
*/
@Override
public void checkReadLock() {
if (this.isIsolated) {
return;
}
super.checkReadLock();
}
/**
* Check the deferred lock on the cache key object.
* This can be called to ensure the cache key has a valid built object.
* It does not hold a lock, so the object could be refreshed afterwards.
*/
@Override
public void checkDeferredLock() {
if (this.isIsolated) {
return;
}
super.checkDeferredLock();
}
/**
* Acquire the read lock on the cache key object.
*/
@Override
public void acquireReadLock() {
if (this.isIsolated) {
return;
}
super.acquireReadLock();
}
/**
* Acquire the read lock on the cache key object. Return true if acquired.
*/
@Override
public boolean acquireReadLockNoWait() {
if (this.isIsolated) {
return true;
}
return super.acquireReadLockNoWait();
}
/**
* INTERNAL:
* Clones itself.
*/
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (Exception exception) {
throw new InternalError(exception.toString());
}
return object;
}
/**
* Determine if the receiver is equal to anObject.
* If anObject is a CacheKey, do further comparison, otherwise, return false.
* @see CacheKey#equals(CacheKey)
*/
@Override
public boolean equals(Object object) {
try {
return equals((CacheKey)object);
} catch (ClassCastException incorrectType) {
return false;
}
}
/**
* Determine if the receiver is equal to key.
* Use an index compare, because it is much faster than enumerations.
*/
public boolean equals(CacheKey key) {
if (this == key) {
return true;
}
if (key.key == null || this.key == null) {
return false;
}
return this.key.equals(key.key);
}
/**
* INTERNAL:
* This method returns the system time in millis seconds at which this object was last refreshed
* CR #4365
* CR #2698903 ... instead of using millis we will now use id's instead. Method
* renamed appropriately.
*/
public long getLastUpdatedQueryId() {
return this.lastUpdatedQueryId;
}
public Object getKey() {
return key;
}
/**
* Return the active thread.
*/
@Override
public Thread getActiveThread() {
if (this.isIsolated) {
if (this.depth.get() > 0) {
return Thread.currentThread();
} else {
return null;
}
}
return super.getActiveThread();
}
public Object getObject() {
return object;
}
public IdentityMap getOwningMap(){
return this.mapOwner;
}
/**
* INTERNAL:
* Return the current value of the Read Time variable
*/
public long getReadTime() {
return readTime;
}
public Record getRecord() {
return record;
}
public Object getWrapper() {
return wrapper;
}
/**
* If a Wrapper subclasses this CacheKey this method will be used to unwrap the cache key.
*/
public CacheKey getWrappedCacheKey(){
return this;
}
public Object getWriteLockValue() {
return writeLockValue;
}
/**
* Overrides hashCode() in Object to use the primaryKey's hashCode for storage in data structures.
*/
@Override
public int hashCode() {
return this.key.hashCode();
}
/**
* Returns true if the protectedForeignKeys record is non-null and non-empty, false otherwise.
*/
public boolean hasProtectedForeignKeys() {
return (this.protectedForeignKeys != null) && (this.protectedForeignKeys.size() > 0);
}
/**
* Returns true if this CacheKey is from an IsolatedClientSession
*/
public boolean isIsolated() {
return isIsolated;
}
/**
* Returns true if this Instance of CacheKey is a wrapper and should be unwrapped before passing
* to IdentityMap APIs. Wrapped CacheKeys may be used in the Cache Interceptors.
*/
public boolean isWrapper(){
return this.isWrapper;
}
/**
* INTERNAL:
* Return the FK cache
*/
public AbstractRecord getProtectedForeignKeys(){
if (this.protectedForeignKeys == null){
this.protectedForeignKeys = new DatabaseRecord();
}
return this.protectedForeignKeys;
}
/**
* INTERNAL:
* Return the value of the invalidationState Variable
* The return value will be a constant
* CHECK_INVALIDATION_POLICY - The Invalidation policy is must be checked for this cache key's sate
* CACHE_KEY_INVALID - This cache key has been labeled invalid.
*/
public int getInvalidationState() {
return invalidationState;
}
/**
* Release the lock on the cache key object.
*/
@Override
public void release() {
if (this.isIsolated) {
this.depth.decrementAndGet();
return;
}
super.release();
}
/**
* Release the deferred lock
*/
@Override
public void releaseDeferredLock() {
if (this.isIsolated) {
this.depth.decrementAndGet();
return;
}
super.releaseDeferredLock();
}
/**
* Release the read lock on the cache key object.
*/
@Override
public void releaseReadLock() {
if (this.isIsolated) {
return;
}
super.releaseReadLock();
}
/**
* Removes this cacheKey from the owning map
*/
public Object removeFromOwningMap(){
if (getOwningMap() != null){
return getOwningMap().remove(this);
}
return null;
}
/**
* INTERNAL:
* Set the value of the invalidationState Variable
* The possible values are from an enumeration of constants
* CHECK_INVALIDATION_POLICY - The invalidation policy is must be checked for this cache key's sate
* CACHE_KEY_INVALID - This cache key has been labelled invalid.
*/
public void setInvalidationState(int invalidationState) {
this.invalidationState = invalidationState;
}
/**
* INTERNAL:
* This method sets the system time in millis seconds at which this object was last refreshed
* CR #4365
* CR #2698903 ... instead of using millis we will now use ids instead. Method
* renamed appropriately.
*/
public void setLastUpdatedQueryId(long id) {
this.lastUpdatedQueryId = id;
}
public void setKey(Object key) {
this.key = key;
}
public void setObject(Object object) {
this.object = object;
}
public void setOwningMap(IdentityMap map){
this.mapOwner = map;
}
public void setProtectedForeignKeys(AbstractRecord protectedForeignKeys) {
this.protectedForeignKeys = protectedForeignKeys;
}
/**
* INTERNAL:
* Set the read time of this cache key
*/
public void setReadTime(long readTime) {
this.readTime = readTime;
invalidationState = CHECK_INVALIDATION_POLICY;
}
public void setRecord(Record newRecord) {
this.record = newRecord;
}
public void setWrapper(Object wrapper) {
this.wrapper = wrapper;
}
public void setWriteLockValue(Object writeLockValue) {
this.writeLockValue = writeLockValue;
}
@Override
public String toString() {
int hashCode = 0;
if (getObject() != null) {
hashCode = getObject().hashCode();
}
return "[" + getKey() + ": " + hashCode + ": " + getWriteLockValue() + ": " + getReadTime() + ": " + getObject() + "]";
}
/**
* Notifies that cache key that it has been accessed.
* Allows the LRU sub-cache to be maintained.
*/
public void updateAccess() {
// Nothing required by default.
}
public void setIsolated(boolean isIsolated) {
this.isIsolated = isIsolated;
}
public void setIsWrapper(boolean isWrapper) {
this.isWrapper = isWrapper;
}
public Object getTransactionId() {
return transactionId;
}
public void setTransactionId(Object transactionId) {
this.transactionId = transactionId;
}
public synchronized Object waitForObject(){
try {
int count = 0;
while (this.object == null && isAcquired()) {
if (count > MAX_WAIT_TRIES)
throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(getActiveThread(), Thread.currentThread());
wait(10);
++count;
}
} catch(InterruptedException ex) {
//ignore as the loop is broken
}
return this.object;
}
}