| /* |
| * 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.DataRecord; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| |
| /** |
| * <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 DataRecord dataRecord; |
| |
| /** 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 DataRecord getRecord() { |
| return dataRecord; |
| } |
| |
| 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(DataRecord newDataRecord) { |
| this.dataRecord = newDataRecord; |
| } |
| |
| 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; |
| } |
| } |