/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* 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 { | |
/** 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. | |
*/ | |
public void acquire() { | |
if (this.isIsolated) { | |
this.depth++; | |
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 | |
*/ | |
public void acquire(boolean forMerge) { | |
if (this.isIsolated) { | |
this.depth++; | |
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 | |
*/ | |
public boolean acquireNoWait() { | |
if (this.isIsolated) { | |
this.depth++; | |
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 > 0) { | |
return false; | |
} | |
this.depth++; | |
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 | |
*/ | |
public boolean acquireNoWait(boolean forMerge) { | |
if (this.isIsolated) { | |
this.depth++; | |
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 | |
*/ | |
public boolean acquireWithWait(boolean forMerge, int wait) { | |
if (this.isIsolated) { | |
this.depth++; | |
return true; | |
} | |
return super.acquireWithWait(forMerge, wait); | |
} | |
/** | |
* Acquire the deferred lock. | |
*/ | |
public void acquireDeferredLock() { | |
if (this.isIsolated) { | |
this.depth++; | |
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. | |
*/ | |
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. | |
*/ | |
public void checkDeferredLock() { | |
if (this.isIsolated) { | |
return; | |
} | |
super.checkDeferredLock(); | |
} | |
/** | |
* Acquire the read lock on the cache key object. | |
*/ | |
public void acquireReadLock() { | |
if (this.isIsolated) { | |
return; | |
} | |
super.acquireReadLock(); | |
} | |
/** | |
* Acquire the read lock on the cache key object. Return true if acquired. | |
*/ | |
public boolean acquireReadLockNoWait() { | |
if (this.isIsolated) { | |
return true; | |
} | |
return super.acquireReadLockNoWait(); | |
} | |
/** | |
* INTERNAL: | |
* Clones itself. | |
*/ | |
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) | |
*/ | |
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; | |
} | |
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. | |
*/ | |
public Thread getActiveThread() { | |
if (this.isIsolated) { | |
if (this.depth > 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. | |
* @return | |
*/ | |
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. | |
*/ | |
public int hashCode() { | |
return this.key.hashCode(); | |
} | |
/** | |
* 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. | |
*/ | |
public void release() { | |
if (this.isIsolated) { | |
this.depth--; | |
return; | |
} | |
super.release(); | |
} | |
/** | |
* Release the deferred lock | |
*/ | |
public void releaseDeferredLock() { | |
if (this.isIsolated) { | |
this.depth--; | |
return; | |
} | |
super.releaseDeferredLock(); | |
} | |
/** | |
* Release the read lock on the cache key object. | |
*/ | |
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; | |
} | |
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; | |
} | |
} |