/******************************************************************************* | |
* 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 java.util.*; | |
import java.util.concurrent.ConcurrentMap; | |
import java.util.concurrent.ConcurrentHashMap; | |
import org.eclipse.persistence.descriptors.ClassDescriptor; | |
import org.eclipse.persistence.indirection.ValueHolderInterface; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.mappings.ForeignReferenceMapping; | |
/** | |
* <p><b>Purpose</b>: A FullIdentityMap holds all objects stored within it for the life of the application. | |
* <p><b>Responsibilities</b>:<ul> | |
* <li> Guarantees identity | |
* <li> Holds all cached objects indefinitely. | |
* </ul> | |
* @since TOPLink/Java 1.0 | |
*/ | |
public class FullIdentityMap extends AbstractIdentityMap { | |
/** Map of CacheKeys stored using their key. */ | |
protected Map<Object, CacheKey> cacheKeys; | |
/** | |
* Used to allow subclasses to build different map type. | |
*/ | |
public FullIdentityMap() { | |
} | |
public FullIdentityMap(int size, ClassDescriptor descriptor, AbstractSession session, boolean isolated) { | |
super(size, descriptor, session, isolated); | |
this.cacheKeys = new ConcurrentHashMap(size); | |
} | |
/** | |
* INTERNAL: | |
* Clones itself. | |
*/ | |
@Override | |
public Object clone() { | |
FullIdentityMap clone = (FullIdentityMap)super.clone(); | |
clone.setCacheKeys(new ConcurrentHashMap(this.cacheKeys.size())); | |
for (Iterator cacheKeysIterator = this.cacheKeys.values().iterator(); cacheKeysIterator.hasNext();) { | |
CacheKey key = (CacheKey)((CacheKey)cacheKeysIterator.next()).clone(); | |
clone.getCacheKeys().put(key.getKey(), key); | |
} | |
return clone; | |
} | |
/** | |
* INTERNAL: | |
* Used to print all the Locks in every identity map in this session. | |
*/ | |
@Override | |
public void collectLocks(HashMap threadList) { | |
Iterator cacheKeyIterator = this.cacheKeys.values().iterator(); | |
while (cacheKeyIterator.hasNext()) { | |
CacheKey cacheKey = (CacheKey)cacheKeyIterator.next(); | |
if (cacheKey.isAcquired()) { | |
Thread activeThread = cacheKey.getActiveThread(); | |
Set set = (Set)threadList.get(activeThread); | |
if (set == null) { | |
set = new HashSet(); | |
threadList.put(activeThread, set); | |
} | |
set.add(cacheKey); | |
} | |
} | |
} | |
/** | |
* Allow for the cache to be iterated on. | |
*/ | |
@Override | |
public Enumeration elements() { | |
return new IdentityMapEnumeration(this); | |
} | |
/** | |
* Return the cache key matching the primary key of the searchKey. | |
* If no object for the key exists, return null. | |
*/ | |
@Override | |
public CacheKey getCacheKey(Object searchKey, boolean forMerge) { | |
return this.cacheKeys.get(searchKey); | |
} | |
/** | |
* 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. | |
*/ | |
@Override | |
protected CacheKey putCacheKeyIfAbsent(CacheKey searchKey) { | |
searchKey.setOwningMap(this); | |
return (CacheKey)((ConcurrentMap)this.cacheKeys).putIfAbsent(searchKey.getKey(), searchKey); | |
} | |
/** | |
* Return the cache keys. | |
*/ | |
public Map<Object, CacheKey> getCacheKeys() { | |
return cacheKeys; | |
} | |
/** | |
* Return the number of CacheKeys in the IdentityMap. | |
* This may contain weak referenced objects that have been garbage collected. | |
*/ | |
@Override | |
public int getSize() { | |
return this.cacheKeys.size(); | |
} | |
/** | |
* Return the number of actual objects of type myClass in the IdentityMap. | |
* Recurse = true will include subclasses of myClass in the count. | |
*/ | |
@Override | |
public int getSize(Class myClass, boolean recurse) { | |
int count = 0; | |
Iterator keys = this.cacheKeys.values().iterator(); | |
while (keys.hasNext()) { | |
CacheKey key = (CacheKey)keys.next(); | |
Object object = key.getObject(); | |
if (object != null) { | |
if (recurse && myClass.isInstance(object)) { | |
count++; | |
} else if (object.getClass().equals(myClass)) { | |
count++; | |
} | |
} | |
} | |
return count; | |
} | |
/** | |
* Allow for the cache keys to be iterated on. | |
* Read locks will be checked. | |
*/ | |
@Override | |
public Enumeration keys() { | |
return keys(true); | |
} | |
/** | |
* Allow for the cache keys to be iterated on. | |
* @param checkReadLocks - true if readLocks should be checked, false otherwise. | |
*/ | |
public Enumeration keys(boolean checkReadLocks) { | |
return new IdentityMapKeyEnumeration(this, checkReadLocks); | |
} | |
/** | |
* Notify the cache that a lazy relationship has been triggered in the object | |
* and the cache may need to be updated | |
*/ | |
public void lazyRelationshipLoaded(Object object, ValueHolderInterface valueHolder, ForeignReferenceMapping mapping){ | |
//NO-OP | |
} | |
/** | |
* 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 CacheKey put(Object primaryKey, Object object, Object writeLockValue, long readTime) { | |
CacheKey newCacheKey = createCacheKey(primaryKey, object, writeLockValue, readTime); | |
// Find the cache key in the map, reset it, or put the new one. | |
CacheKey cacheKey = putCacheKeyIfAbsent(newCacheKey); | |
if (cacheKey != null) { | |
// The cache key is locked inside resetCacheKey() to keep other threads from accessing the object. | |
resetCacheKey(cacheKey, object, writeLockValue, readTime); | |
} else { | |
return newCacheKey; | |
} | |
return cacheKey; | |
} | |
/** | |
* Removes the CacheKey from the map. | |
* @return the object held within the CacheKey or null if no object cached for given cacheKey. | |
*/ | |
@Override | |
public Object remove(CacheKey cacheKey) { | |
if (cacheKey != null) { | |
// Cache key needs to be locked when removing from the map. | |
cacheKey.acquire(); | |
this.cacheKeys.remove(cacheKey.getKey()); | |
cacheKey.setOwningMap(null); | |
// Cache key needs to be released after removing from the map. | |
cacheKey.setInvalidationState(CacheKey.CACHE_KEY_INVALID); | |
cacheKey.release(); | |
return cacheKey.getObject(); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Reset the cache key with new data. | |
*/ | |
public void resetCacheKey(CacheKey key, Object object, Object writeLockValue, long readTime) { | |
key.acquire(); | |
key.setObject(object); | |
key.setWriteLockValue(writeLockValue); | |
key.setReadTime(readTime); | |
key.release(); | |
} | |
protected void setCacheKeys(Map<Object, CacheKey> cacheKeys) { | |
this.cacheKeys = cacheKeys; | |
} | |
} |