All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.identitymaps.FullIdentityMap Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 1998, 2024 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 org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 

Purpose: A FullIdentityMap holds all objects stored within it for the life of the application. *

Responsibilities:

    *
  • Guarantees identity *
  • Holds all cached objects indefinitely. *
* @since TOPLink/Java 1.0 */ public class FullIdentityMap extends AbstractIdentityMap { /** Map of CacheKeys stored using their key. */ protected Map 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 IdentityMap 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) 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(Map> threadList) { Iterator cacheKeyIterator = this.cacheKeys.values().iterator(); while (cacheKeyIterator.hasNext()) { CacheKey cacheKey = cacheKeyIterator.next(); if (cacheKey.isAcquired()) { Thread activeThread = cacheKey.getActiveThread(); Set set = threadList.computeIfAbsent(activeThread, k -> new HashSet<>()); 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 this.cacheKeys.putIfAbsent(searchKey.getKey(), searchKey); } /** * Return the cache keys. */ public Map 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 = 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 {@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 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 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 cacheKeys) { this.cacheKeys = cacheKeys; } }