| /* |
| * Copyright (c) 1998, 2020 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 |
| package org.eclipse.persistence.internal.identitymaps; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| |
| /** |
| * <p><b>Purpose</b>: A fixed size LRU cache<p> |
| * Using a linked list as well as the map from the superclass a LRU cache is maintained. |
| * When a get is executed the LRU list is updated and when a new object is inserted the object |
| * at the start of the list is deleted (provided the maxSize has been reached). |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Guarantees identity through primary key values |
| * <li> Keeps the LRU linked list updated. |
| * </ul> |
| * @since TOPLink/Java 1.0 |
| */ |
| public class CacheIdentityMap extends FullIdentityMap { |
| |
| /** Provide handles on the linked list */ |
| protected LinkedCacheKey first; |
| |
| /** Provide handles on the linked list */ |
| protected LinkedCacheKey last; |
| |
| public CacheIdentityMap(int size, ClassDescriptor descriptor, AbstractSession session, boolean isolated) { |
| super(size, descriptor, session, isolated); |
| this.first = new LinkedCacheKey(CacheId.EMPTY, null, null, 0, isIsolated); |
| this.last = new LinkedCacheKey(CacheId.EMPTY, null, null, 0, isIsolated); |
| this.first.setNext(this.last); |
| this.last.setPrevious(this.first); |
| } |
| |
| @Override |
| public CacheKey createCacheKey(Object primaryKey, Object object, Object writeLockValue, long readTime) { |
| return new LinkedCacheKey(primaryKey, object, writeLockValue, readTime, isIsolated); |
| } |
| |
| /** |
| * Reduces the size of the receiver down to the maxSize removing objects from the |
| * start of the linked list. |
| */ |
| protected void ensureFixedSize() { |
| // protect the case where someone attempts to break the cache by |
| // setting max size to 0. |
| synchronized(this.first) { |
| while (getMaxSize() > 0 && getSize() > getMaxSize()) { |
| remove(last.getPrevious()); |
| } |
| } |
| } |
| |
| /** |
| * Access the object within the table for the given primaryKey. |
| * Move the accessed key to the top of the order keys linked list to maintain LRU. |
| * @param primaryKeys is the primary key for the object to search for. |
| * @return the LinkedCacheKey or null if none found for primaryKey |
| */ |
| @Override |
| public CacheKey getCacheKey(Object primaryKeys, boolean forMerge) { |
| LinkedCacheKey cacheKey = (LinkedCacheKey)super.getCacheKey(primaryKeys, forMerge); |
| if (cacheKey != null) { |
| synchronized (this.first) { |
| removeLink(cacheKey); |
| insertLink(cacheKey); |
| } |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Insert a new element into the linked list of LinkedCacheKeys. |
| * New elements (Recently Used) are added at the end (last). |
| * Callers of this method must synchronize on the start of the list (this.first). |
| * @return the added LinkedCacheKey |
| */ |
| protected LinkedCacheKey insertLink(LinkedCacheKey key) { |
| if (key == null){ |
| return key; |
| } |
| this.first.getNext().setPrevious(key); |
| key.setNext(this.first.getNext()); |
| key.setPrevious(this.first); |
| this.first.setNext(key); |
| return key; |
| } |
| |
| /** |
| * Also insert the link if the cacheKey is put. |
| */ |
| @Override |
| protected CacheKey putCacheKeyIfAbsent(CacheKey searchKey) { |
| synchronized(this.first) { |
| CacheKey cacheKey = super.putCacheKeyIfAbsent(searchKey); |
| if (cacheKey == null) { |
| insertLink((LinkedCacheKey)searchKey); |
| ensureFixedSize(); |
| } |
| return cacheKey; |
| } |
| } |
| |
| /** |
| * Remove the LinkedCacheKey from the cache as well as from the linked list. |
| * @return the LinkedCacheKey to be removed. |
| */ |
| @Override |
| public Object remove(CacheKey key) { |
| synchronized (this.first) { |
| super.remove(key); |
| // The key may be null if was missing, just null should be returned in this case. |
| if (key == null) { |
| return null; |
| } |
| return removeLink((LinkedCacheKey)key).getObject(); |
| } |
| } |
| |
| /** |
| * Remove the LinkedCacheKey from the linked list. |
| * Callers of this method must synchronize on the start of the list (this.first). |
| * @return the removed LinkedCacheKey. |
| */ |
| protected LinkedCacheKey removeLink(LinkedCacheKey key) { |
| // callers of this method must be synchronized on this.first |
| if (key == null || key.getPrevious() == null || key.getNext() == null){ |
| //already removed by a competing thread, just return |
| return key; |
| } |
| key.getPrevious().setNext(key.getNext()); |
| key.getNext().setPrevious(key.getPrevious()); |
| key.setNext(null); |
| key.setPrevious(null); |
| return key; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will be used to update the max cache size, any objects exceeding the max cache size will |
| * be remove from the cache. Please note that this does not remove the object from the identityMap, except in |
| * the case of the CacheIdentityMap. |
| */ |
| @Override |
| public synchronized void updateMaxSize(int maxSize) { |
| setMaxSize(maxSize); |
| ensureFixedSize(); |
| } |
| } |