| /* |
| * 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: |
| // Gordon Yorke - Initial Feature development |
| // 12/14/2017-3.0 Tomas Kraus |
| // - 522635: ConcurrentModificationException when triggering lazy load from conforming query |
| package org.eclipse.persistence.sessions.interceptors; |
| |
| import java.util.Collection; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.indirection.ValueHolderInterface; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.identitymaps.IdentityMap; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Define a class through which Cache access can be |
| * intercepted. |
| * <p> |
| * <b>Description</b>: EclipseLink makes extensive use of caching |
| * functionality. This class provides the mechanism for intercepting the cache |
| * access. Once intercepted applications can allow the process to continue by |
| * calling the method on the class attribute targetIdentityMap as described in |
| * the API javadocs or redirect the process entirely. * As with IdentityMaps an |
| * entire class inheritance hierarchy will share the same interceptor. |
| * <p> |
| * To implement a CacheInterceptor users should subclass this class implementing |
| * those methods they wish to intercept. |
| * <p> |
| * <b>Configuration</b>: Interceptors may be added to a session by using a |
| * Session or Descriptor customizer to set the class name of the interceptor on |
| * the target ClassDescriptor through the method |
| * setCacheInterceptorClass(Class). |
| */ |
| public abstract class CacheInterceptor implements IdentityMap { |
| /** |
| * This attribute stores the actual IdentityMap as configured by the user. |
| */ |
| protected IdentityMap targetIdentityMap; |
| |
| protected AbstractSession interceptedSession; |
| |
| protected CacheInterceptor(IdentityMap targetIdentityMap, AbstractSession interceptedSession){ |
| this.targetIdentityMap = targetIdentityMap; |
| this.interceptedSession = interceptedSession; |
| } |
| /** |
| * Acquire a deferred lock on the object. |
| * This is used while reading if the object has relationships without indirection. |
| * This first thread will get an active lock. |
| * Other threads will get deferred locks, all threads will wait until all other threads are complete before releasing their locks. |
| */ |
| @Override |
| public CacheKey acquireDeferredLock(Object primaryKey, boolean isCacheCheckComplete){ |
| return createCacheKeyInterceptor(this.targetIdentityMap.acquireDeferredLock(primaryKey, isCacheCheckComplete)); |
| } |
| |
| /** |
| * Acquire an active lock on the object. |
| * This is used by reading (when using indirection or no relationships) and by merge. |
| */ |
| @Override |
| public CacheKey acquireLock(Object primaryKey, boolean forMerge, boolean isCacheCheckComplete){ |
| return createCacheKeyInterceptor(this.targetIdentityMap.acquireLock(primaryKey, forMerge, isCacheCheckComplete)); |
| } |
| |
| /** |
| * Acquire an active lock on the object, if not already locked. |
| * This is used by merge for missing existing objects. |
| */ |
| @Override |
| public CacheKey acquireLockNoWait(Object primaryKey, boolean forMerge){ |
| CacheKey cacheKeyToBeWrapped = this.targetIdentityMap.acquireLockNoWait(primaryKey, forMerge); |
| if (cacheKeyToBeWrapped != null){ |
| return createCacheKeyInterceptor(cacheKeyToBeWrapped); |
| } |
| return null; |
| } |
| |
| /** |
| * Acquire an active lock on the object, if not already locked. |
| * This is used by merge for missing existing objects. |
| */ |
| @Override |
| public CacheKey acquireLockWithWait(Object primaryKey, boolean forMerge, int wait) { |
| CacheKey cacheKeyToBeWrapped = this.targetIdentityMap.acquireLockWithWait(primaryKey, forMerge, wait); |
| if (cacheKeyToBeWrapped != null) { |
| return createCacheKeyInterceptor(cacheKeyToBeWrapped); |
| } |
| return null; |
| } |
| |
| /** |
| * Acquire a read lock on the object. |
| * This is used by UnitOfWork cloning. |
| * This will allow multiple users to read the same object but prevent writes to the object while the read lock is held. |
| */ |
| @Override |
| public CacheKey acquireReadLockOnCacheKey(Object primaryKey) { |
| return createCacheKeyInterceptor(this.targetIdentityMap.acquireReadLockOnCacheKey(primaryKey)); |
| } |
| |
| /** |
| * Acquire a read lock on the object, if not already locked. |
| * This is used by UnitOfWork cloning. |
| * This will allow multiple users to read the same object but prevent writes to the object while the read lock is held. |
| */ |
| @Override |
| public CacheKey acquireReadLockOnCacheKeyNoWait(Object primaryKey) { |
| CacheKey cacheKeyToBeWrapped = this.targetIdentityMap.acquireReadLockOnCacheKeyNoWait(primaryKey); |
| if (cacheKeyToBeWrapped != null){ |
| return createCacheKeyInterceptor(cacheKeyToBeWrapped); |
| } |
| return null; |
| } |
| |
| /** |
| * Add all locked CacheKeys to the map grouped by thread. |
| * Used to print all the locks in the identity map. |
| */ |
| @Override |
| public void collectLocks(HashMap threadList) { |
| this.targetIdentityMap.collectLocks(threadList); |
| } |
| |
| /** |
| * Clone the map and all of the CacheKeys. |
| * This is used by UnitOfWork commitAndResumeOnFailure to avoid corrupting the cache during a failed commit. |
| */ |
| @Override |
| public abstract Object clone(); |
| |
| /** |
| * Return true if an CacheKey with the primary key is in the map. |
| * User API. |
| * @param primaryKey is the primary key for the object to search for. |
| */ |
| @Override |
| public boolean containsKey(Object primaryKey) { |
| return this.targetIdentityMap.containsKey(primaryKey); |
| } |
| |
| |
| protected abstract CacheKeyInterceptor createCacheKeyInterceptor(CacheKey wrappedCacheKey); |
| |
| /** |
| * Allow for the cache to be iterated on. This method is only used during debugging when |
| * validateCache() has been called to print out the contents of the cache. |
| */ |
| @Override |
| public Enumeration elements() { |
| return this.targetIdentityMap.elements(); |
| } |
| |
| /** |
| * Return the object cached in the identity map or null if it could not be found. |
| * User API. |
| */ |
| @Override |
| public Object get(Object primaryKey){ |
| return this.targetIdentityMap.get(primaryKey); |
| } |
| |
| /** |
| * ADVANCED: |
| * Using a list of Entity PK this method will attempt to bulk load the entire list from the cache. |
| * In certain circumstances this can have large performance improvements over loading each item individually. |
| * @param pkList List of Entity PKs to extract from the cache |
| * @param descriptor Descriptor type to be retrieved. |
| * @return Map of Entity PKs associated to the Entities that were retrieved |
| */ |
| @Override |
| public abstract Map<Object, Object> getAllFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session); |
| |
| /** |
| * ADVANCED: |
| * Using a list of Entity PK this method will attempt to bulk load the entire list from the cache. |
| * In certain circumstances this can have large performance improvements over loading each item individually. |
| * @param pkList List of Entity PKs to extract from the cache |
| * @param descriptor Descriptor type to be retrieved. |
| * @return Map of Entity PKs associated to the Entities that were retrieved |
| */ |
| @Override |
| public abstract Map<Object, CacheKey> getAllCacheKeysFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session); |
| |
| /** |
| * Get the cache key (with object) for the primary key. |
| */ |
| @Override |
| public CacheKey getCacheKey(Object primaryKey, boolean forMerge) { |
| return this.targetIdentityMap.getCacheKey(primaryKey, forMerge); |
| } |
| |
| /** |
| * Get the cache key (with object) for the primary key. |
| */ |
| @Override |
| public CacheKey getCacheKeyForLock(Object primaryKey) { |
| return this.targetIdentityMap.getCacheKeyForLock(primaryKey); |
| } |
| |
| /** |
| * Return the descriptor that this is the map for. |
| */ |
| @Override |
| public ClassDescriptor getDescriptor() { |
| return this.targetIdentityMap.getDescriptor(); |
| } |
| |
| /** |
| * Return the class that this is the map for. |
| */ |
| @Override |
| public Class getDescriptorClass() { |
| return this.targetIdentityMap.getDescriptorClass(); |
| } |
| |
| /** |
| * @return The maxSize for the IdentityMap (NOTE: some subclasses may use this differently). |
| */ |
| @Override |
| public int getMaxSize() { |
| return this.targetIdentityMap.getMaxSize(); |
| } |
| |
| /** |
| * 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.targetIdentityMap.getSize(); |
| } |
| |
| /** |
| * 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) { |
| return this.targetIdentityMap.getSize(myClass, recurse); |
| } |
| |
| /** |
| * Return the instance of the IdentityMap that this intercpetor is wrapping. Any call that |
| * is to be passed on to the underlying EclipseLink cache should be passed to this IdentityMap. |
| */ |
| public IdentityMap getTargetIdenttyMap() { |
| return this.targetIdentityMap; |
| } |
| |
| /** |
| * Get the wrapper object from the cache key associated with the given primary key, |
| * this is used for EJB2. |
| */ |
| @Override |
| public Object getWrapper(Object primaryKey) { |
| return this.targetIdentityMap.getWrapper(primaryKey); |
| } |
| |
| /** |
| * Get the write lock value from the cache key associated to the primarykey. |
| * User API. |
| */ |
| @Override |
| public Object getWriteLockValue(Object primaryKey) { |
| return this.targetIdentityMap.getWriteLockValue(primaryKey); |
| } |
| |
| /** |
| * Allow for the {@link CacheKey} elements to be iterated. |
| * {@link CacheKey} {@link Collection} is reused so this iteration may not be thread safe. |
| * |
| * @return {@link Enumeration} of {@link CacheKey} instances. |
| */ |
| @Override |
| public Enumeration<CacheKey> keys() { |
| return this.targetIdentityMap.keys(); |
| } |
| |
| /** |
| * 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 this.targetIdentityMap.cloneKeys(); |
| } |
| |
| /** |
| * 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 this.targetIdentityMap.keys(checkReadLocks); |
| } |
| |
| /** |
| * 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 rootEntity, ValueHolderInterface valueHolder, ForeignReferenceMapping mapping){ |
| this.targetIdentityMap.lazyRelationshipLoaded(rootEntity, valueHolder, mapping); |
| } |
| |
| /** |
| * 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) { |
| return this.targetIdentityMap.put(primaryKey, object, writeLockValue, readTime); |
| } |
| |
| /** |
| * Remove the CacheKey with the primaryKey from the map. |
| * This is used by DeleteObjectQuery and merge. |
| * This is also an advanced (very) user API. |
| */ |
| @Override |
| public Object remove(Object primaryKey, Object object) { |
| return this.targetIdentityMap.remove(primaryKey, object); |
| } |
| |
| /** |
| * Remove the CacheKey from the map. |
| */ |
| @Override |
| public Object remove(CacheKey cacheKey) { |
| if (cacheKey.isWrapper()){ |
| return this.targetIdentityMap.remove(cacheKey.getWrappedCacheKey()); |
| } |
| return this.targetIdentityMap.remove(cacheKey); |
| } |
| |
| /** |
| * 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 void updateMaxSize(int maxSize){ |
| this.targetIdentityMap.updateMaxSize(maxSize); |
| } |
| |
| /** |
| * Set the descriptor that this is the map for. |
| */ |
| @Override |
| public void setDescriptor(ClassDescriptor descriptor) { |
| this.targetIdentityMap.setDescriptor(descriptor); |
| } |
| |
| /** |
| * Update the wrapper object in the CacheKey associated with the given primaryKey, |
| * this is used for EJB2. |
| */ |
| @Override |
| public void setWrapper(Object primaryKey, Object wrapper) { |
| this.targetIdentityMap.setWrapper(primaryKey, wrapper); |
| } |
| |
| /** |
| * Update the write lock value of the CacheKey associated with the given primaryKey. |
| * This is used by UpdateObjectQuery, and is also an advanced (very) user API. |
| */ |
| @Override |
| public void setWriteLockValue(Object primaryKey, Object writeLockValue) { |
| this.targetIdentityMap.setWriteLockValue(primaryKey, writeLockValue); |
| } |
| |
| @Override |
| public String toString() { |
| return Helper.getShortClassName("Intercepted " + this.targetIdentityMap.getClass()) + "[" + getSize() + "]"; |
| } |
| } |