| /* |
| * 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 |
| // 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<CacheKey> cacheKeysIterator = this.cacheKeys.values().iterator(); cacheKeysIterator.hasNext();) { |
| CacheKey key = (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<CacheKey> cacheKeyIterator = this.cacheKeys.values().iterator(); |
| while (cacheKeyIterator.hasNext()) { |
| 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<CacheKey> keys = this.cacheKeys.values().iterator(); |
| |
| while (keys.hasNext()) { |
| CacheKey key = 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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; |
| } |
| } |