blob: 98521b01b4e37920bd9f55d06d04998d538c091e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2017 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
* 12/14/2017-3.0 Tomas Kraus
* - 522635: ConcurrentModificationException when triggering lazy load from conforming query
******************************************************************************/
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 {@link CacheKey#getObject()} elements to be iterated.
*
* @return {@link Enumeration} of {@link CacheKey#getObject()} instances.
*/
@Override
public Enumeration elements() {
return new IdentityMapEnumeration(this.getCacheKeys().values());
}
/**
* 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<CacheKey> keys() {
return keys(true);
}
/**
* Allow for the {@link CacheKey} elements to be iterated.
* {@link CacheKey} {@link Collection} is reused so this iteration may not be thread safe.
*
* @param checkReadLocks value of {@code true} if read lock on the {@link CacheKey}
* instances should be checked or {@code false} otherwise
* @return {@link Enumeration} of {@link CacheKey} instances.
*/
public Enumeration<CacheKey> keys(boolean checkReadLocks) {
return new IdentityMapKeyEnumeration(this.getCacheKeys().values(), checkReadLocks);
}
/**
* Allow for the {@link CacheKey} elements to be iterated.
* Clone of {@link CacheKey} {@link Collection} is returned so this iteration should
* be thread safe.
*
* @return {@link Enumeration} with clone of the {@link CacheKey} {@link Collection}
*/
@Override
public Enumeration<CacheKey> cloneKeys() {
return new IdentityMapKeyEnumeration(new ArrayList(this.getCacheKeys().values()), true);
}
/**
* 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;
}
}