| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2021 IBM Corporation. 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 |
| // Mark Wolochuk - Bug 321041 ConcurrentModificationException on getFromIdentityMap() fix |
| // 11/07/2017 - Dalia Abo Sheasha |
| // - 526957: Split the logging and trace messages |
| // 12/14/2017-3.0 Tomas Kraus |
| // - 522635: ConcurrentModificationException when triggering lazy load from conforming query |
| package org.eclipse.persistence.internal.identitymaps; |
| |
| import java.io.PrintWriter; |
| import java.io.Serializable; |
| import java.io.StringWriter; |
| import java.lang.reflect.Constructor; |
| import java.security.AccessController; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.eclipse.persistence.config.ReferenceMode; |
| import org.eclipse.persistence.descriptors.CacheIndex; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.invalidation.CacheInvalidationPolicy; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.EclipseLinkException; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.ConcurrencyManager; |
| import org.eclipse.persistence.internal.helper.DeferredLockManager; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.helper.InvalidObject; |
| import org.eclipse.persistence.internal.helper.WriteLockManager; |
| import org.eclipse.persistence.internal.localization.LoggingLocalization; |
| import org.eclipse.persistence.internal.localization.TraceLocalization; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor; |
| import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.queries.InMemoryQueryIndirectionPolicy; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.sessions.Record; |
| import org.eclipse.persistence.sessions.SessionProfiler; |
| |
| /** |
| * <p><b>Purpose</b>: Maintain identity maps for domain classes mapped with EclipseLink. |
| * <p><b>Responsibilities</b>:<ul> |
| * <li> Build new identity maps lazily using info from the descriptor |
| * <li> Insert objects into appropriate identity map |
| * <li> Get object from appropriate identity map using object or primary key with class |
| * <li> Get and Set write lock values for cached objects |
| * </ul> |
| * @since TOPLink/Java 1.0 |
| */ |
| public class IdentityMapManager implements Serializable, Cloneable { |
| protected static final String MONITOR_PREFIX = SessionProfiler.CacheSize; |
| |
| /** A table of identity maps with the key being the domain Class. */ |
| protected Map<Class, IdentityMap> identityMaps; |
| |
| /** A table of identity maps with the key being the query */ |
| protected Map<Object, IdentityMap> queryResults; |
| |
| /** A map of class to list of queries that need to be invalidated when that class changes. */ |
| protected Map<Class, Set> queryResultsInvalidationsByClass; |
| |
| /** A map of indexes on the cache. */ |
| protected Map<CacheIndex, IdentityMap> cacheIndexes; |
| |
| /** A reference to the session owning this manager. */ |
| protected AbstractSession session; |
| |
| /** Ensure mutual exclusion depending on the cache isolation.*/ |
| protected transient ConcurrencyManager cacheMutex; |
| |
| /** PERF: Optimize the object retrieval from the identity map. */ |
| protected IdentityMap lastAccessedIdentityMap = null; |
| |
| /** Used to store the write lock manager used for merging. */ |
| protected transient WriteLockManager writeLockManager; |
| |
| /** PERF: Used to avoid readLock and profiler checks to improve performance. */ |
| protected boolean isCacheAccessPreCheckRequired; |
| |
| protected IdentityMapManager() { |
| } |
| |
| public IdentityMapManager(AbstractSession session) { |
| this.session = session; |
| this.cacheMutex = new ConcurrencyManager(); |
| // PERF: Avoid query cache for uow as never used. |
| if (session.isUnitOfWork()) { |
| this.identityMaps = new HashMap(); |
| } else if (session.isIsolatedClientSession()) { |
| this.identityMaps = new HashMap(); |
| this.queryResults = new HashMap(); |
| this.queryResultsInvalidationsByClass = new HashMap(); |
| this.cacheIndexes = new HashMap(); |
| } else { |
| this.identityMaps = new ConcurrentHashMap(); |
| this.queryResults = new ConcurrentHashMap(); |
| this.queryResultsInvalidationsByClass = new ConcurrentHashMap(); |
| this.cacheIndexes = new ConcurrentHashMap(); |
| } |
| checkIsCacheAccessPreCheckRequired(); |
| } |
| |
| /** |
| * Provides access for setting a deferred lock on an object in the IdentityMap. |
| */ |
| public CacheKey acquireDeferredLock(Object primaryKey, Class domainClass, ClassDescriptor descriptor, boolean isCacheCheckComplete) { |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| IdentityMap identityMap = getIdentityMap(descriptor, false); |
| try { |
| cacheKey = identityMap.acquireDeferredLock(primaryKey, isCacheCheckComplete); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| if (!this.session.isUnitOfWork() && cacheKey.getObject() == null) { |
| this.session.updateProfile(MONITOR_PREFIX + domainClass.getSimpleName(), identityMap.getSize()); |
| } |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireDeferredLock(primaryKey, isCacheCheckComplete); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * Provides access for setting a concurrency lock on an object in the IdentityMap. |
| * called with true from the merge process, if true then the refresh will not refresh the object. |
| */ |
| public CacheKey acquireLock(Object primaryKey, Class domainClass, boolean forMerge, ClassDescriptor descriptor, boolean isCacheCheckComplete) { |
| if (primaryKey == null) { |
| CacheKey cacheKey = new CacheKey(null); |
| cacheKey.acquire(); |
| return cacheKey; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| IdentityMap identityMap = getIdentityMap(descriptor, false); |
| try { |
| cacheKey = identityMap.acquireLock(primaryKey, forMerge, isCacheCheckComplete); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| if (!this.session.isUnitOfWork() && (cacheKey != null) && cacheKey.getObject() == null) { |
| this.session.updateProfile(MONITOR_PREFIX + domainClass.getSimpleName(), identityMap.getSize()); |
| } |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireLock(primaryKey, forMerge, isCacheCheckComplete); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * Provides access for setting a concurrency lock on an object in the IdentityMap. |
| * called with true from the merge process, if true then the refresh will not refresh the object. |
| */ |
| public CacheKey acquireLockNoWait(Object primaryKey, Class domainClass, boolean forMerge, ClassDescriptor descriptor) { |
| if (primaryKey == null) { |
| CacheKey cacheKey = new CacheKey(null); |
| cacheKey.acquire(); |
| return cacheKey; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| IdentityMap identityMap = getIdentityMap(descriptor, false); |
| try { |
| cacheKey = identityMap.acquireLockNoWait(primaryKey, forMerge); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| if (!this.session.isUnitOfWork() && (cacheKey != null) && (cacheKey.getObject() == null)) { |
| this.session.updateProfile(MONITOR_PREFIX + domainClass.getSimpleName(), identityMap.getSize()); |
| } |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireLockNoWait(primaryKey, forMerge); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * Provides access for setting a concurrency lock on an object in the IdentityMap. |
| * called with true from the merge process, if true then the refresh will not refresh the object. |
| */ |
| public CacheKey acquireLockWithWait(Object primaryKey, Class domainClass, boolean forMerge, ClassDescriptor descriptor, int wait) { |
| if (primaryKey == null) { |
| CacheKey cacheKey = new CacheKey(null); |
| cacheKey.acquire(); |
| return cacheKey; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| IdentityMap identityMap = getIdentityMap(descriptor, false); |
| try { |
| cacheKey = identityMap.acquireLockWithWait(primaryKey, forMerge, wait); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| if (!this.session.isUnitOfWork() && (cacheKey != null) && cacheKey.getObject() == null) { |
| this.session.updateProfile(MONITOR_PREFIX + domainClass.getSimpleName(), identityMap.getSize()); |
| } |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireLockWithWait(primaryKey, forMerge, wait); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * PERF: Used to micro optimize cache access. |
| * Avoid the readLock and profile checks if not required. |
| */ |
| public void checkIsCacheAccessPreCheckRequired() { |
| if ((this.session.getProfiler() != null) |
| || ((this.session.getDatasourceLogin() != null) && this.session.getDatasourceLogin().shouldSynchronizedReadOnWrite())) { |
| this.isCacheAccessPreCheckRequired = true; |
| } else { |
| this.isCacheAccessPreCheckRequired = false; |
| } |
| } |
| |
| /** |
| * Provides access for setting a concurrency lock on an IdentityMap. |
| */ |
| public void acquireReadLock() { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| |
| if (this.session.getDatasourceLogin().shouldSynchronizedReadOnWrite()) { |
| getCacheMutex().acquireReadLock(); |
| } |
| |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| |
| /** |
| * INTERNAL: |
| * Find the cachekey for the provided primary key and place a readlock on it. |
| * This will allow multiple users to read the same object but prevent writes to |
| * the object while the read lock is held. |
| */ |
| public CacheKey acquireReadLockOnCacheKey(Object primaryKey, Class domainClass, ClassDescriptor descriptor) { |
| if (primaryKey == null) { |
| CacheKey cacheKey = new CacheKey(null); |
| cacheKey.acquireReadLock(); |
| return cacheKey; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = getIdentityMap(descriptor, false).acquireReadLockOnCacheKey(primaryKey); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireReadLockOnCacheKey(primaryKey); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * INTERNAL: |
| * Find the cachekey for the provided primary key and place a readlock on it. |
| * This will allow multiple users to read the same object but prevent writes to |
| * the object while the read lock is held. |
| * If no readlock can be acquired then do not wait but return null. |
| */ |
| public CacheKey acquireReadLockOnCacheKeyNoWait(Object primaryKey, Class domainClass, ClassDescriptor descriptor) { |
| if (primaryKey == null) { |
| CacheKey cacheKey = new CacheKey(null); |
| cacheKey.acquireReadLock(); |
| return cacheKey; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = getIdentityMap(descriptor, false).acquireReadLockOnCacheKeyNoWait(primaryKey); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| cacheKey = getIdentityMap(descriptor, false).acquireReadLockOnCacheKeyNoWait(primaryKey); |
| } |
| |
| return cacheKey; |
| } |
| |
| /** |
| * Lock the entire cache if the cache isolation requires. |
| * By default concurrent reads and writes are allowed. |
| * By write, unit of work merge is meant. |
| */ |
| public boolean acquireWriteLock() { |
| if (this.session.getDatasourceLogin().shouldSynchronizedReadOnWrite() || this.session.getDatasourceLogin().shouldSynchronizeWrites()) { |
| getCacheMutex().acquire(); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: (Public to allow testing to access) |
| * Return a new empty identity map to cache instances of the class. |
| */ |
| public IdentityMap buildNewIdentityMap(ClassDescriptor descriptor) { |
| if (this.session.isUnitOfWork()) { |
| ReferenceMode mode = ((UnitOfWorkImpl)this.session).getReferenceMode(); |
| if (mode == ReferenceMode.FORCE_WEAK){ |
| return new WeakUnitOfWorkIdentityMap(32, descriptor, this.session, true); |
| } else if (mode == ReferenceMode.WEAK && descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy()) { |
| return new WeakUnitOfWorkIdentityMap(32, descriptor, this.session, true); |
| } else { |
| return new UnitOfWorkIdentityMap(32, descriptor, this.session, true); |
| } |
| } |
| |
| // Remote session has its own setting. |
| if (this.session.isRemoteSession()) { |
| return buildNewIdentityMap(descriptor.getRemoteIdentityMapClass(), descriptor.getRemoteIdentityMapSize(), descriptor, true); |
| } else { |
| return buildNewIdentityMap(descriptor.getIdentityMapClass(), descriptor.getIdentityMapSize(), descriptor, this.session.isIsolatedClientSession()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a new empty identity map of the class type. |
| */ |
| protected IdentityMap buildNewIdentityMap(Class identityMapClass, int size, ClassDescriptor descriptor, boolean isIsolated) throws DescriptorException { |
| if ((descriptor == null) || (descriptor.getCachePolicy().getCacheInterceptorClass() == null)) { |
| // PERF: Avoid reflection. |
| if (identityMapClass == ClassConstants.SoftCacheWeakIdentityMap_Class) { |
| return new SoftCacheWeakIdentityMap(size, descriptor, this.session, isIsolated); |
| } else if (identityMapClass == ClassConstants.HardCacheWeakIdentityMap_Class) { |
| return new HardCacheWeakIdentityMap(size, descriptor, this.session, isIsolated); |
| } else if (identityMapClass == ClassConstants.SoftIdentityMap_Class) { |
| return new SoftIdentityMap(size, descriptor, this.session, isIsolated); |
| } else if (identityMapClass == ClassConstants.WeakIdentityMap_Class) { |
| return new WeakIdentityMap(size, descriptor, this.session, isIsolated); |
| } else if (identityMapClass == ClassConstants.FullIdentityMap_Class) { |
| return new FullIdentityMap(size, descriptor, this.session, isIsolated); |
| } else if (identityMapClass == ClassConstants.CacheIdentityMap_Class) { |
| return new CacheIdentityMap(size, descriptor, this.session, isIsolated); |
| } |
| } |
| try { |
| Class[] parameters = new Class[]{ClassConstants.PINT, ClassDescriptor.class, AbstractSession.class, boolean.class}; |
| Object[] values = new Object[]{size, descriptor, this.session, isIsolated}; |
| if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) { |
| Constructor constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(identityMapClass, parameters, false)); |
| IdentityMap map = (IdentityMap)AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, values)); |
| if ((descriptor != null) && (descriptor.getCachePolicy().getCacheInterceptorClass() != null)) { |
| constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(descriptor.getCacheInterceptorClass(), new Class[] { IdentityMap.class, AbstractSession.class }, false)); |
| Object params[] = new Object[]{map, this.session}; |
| map = (IdentityMap)AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, params)); |
| } |
| return map; |
| } else { |
| Constructor constructor = PrivilegedAccessHelper.getConstructorFor(identityMapClass, parameters, false); |
| IdentityMap map = (IdentityMap)PrivilegedAccessHelper.invokeConstructor(constructor, values); |
| if ((descriptor != null) && (descriptor.getCacheInterceptorClass() != null)) { |
| constructor = PrivilegedAccessHelper.getConstructorFor(descriptor.getCacheInterceptorClass(), new Class[] { IdentityMap.class, AbstractSession.class }, false); |
| Object params[] = new Object[]{map, this.session}; |
| map = (IdentityMap)PrivilegedAccessHelper.invokeConstructor(constructor, params); |
| } |
| return map; |
| } |
| } catch (Exception exception) { |
| throw DescriptorException.invalidIdentityMap(descriptor, exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Clear the the lastAccessedIdentityMap and the lastAccessedIdentityMapClass |
| */ |
| public void clearLastAccessedIdentityMap() { |
| this.lastAccessedIdentityMap = null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Clones itself, used for uow commit and resume on failure. |
| */ |
| @Override |
| public Object clone() { |
| IdentityMapManager manager = null; |
| try { |
| manager = (IdentityMapManager)super.clone(); |
| manager.setIdentityMaps(new ConcurrentHashMap()); |
| for (Iterator iterator = this.identityMaps.entrySet().iterator(); iterator.hasNext();) { |
| Map.Entry entry = (Map.Entry)iterator.next(); |
| manager.identityMaps.put((Class)entry.getKey(), (IdentityMap)((IdentityMap)entry.getValue()).clone()); |
| } |
| } catch (CloneNotSupportedException exception) { |
| throw new InternalError(exception.toString()); |
| } |
| return manager; |
| } |
| |
| /** |
| * Clear all the query caches. |
| */ |
| public void clearQueryCache() { |
| this.queryResults = new ConcurrentHashMap(); |
| this.queryResultsInvalidationsByClass = new ConcurrentHashMap(); |
| } |
| |
| /** |
| * Clear all index caches. |
| */ |
| public void clearCacheIndexes() { |
| this.cacheIndexes = new ConcurrentHashMap(); |
| } |
| |
| /** |
| * Remove the cache key related to a query. |
| * Note this method is not synchronized and care should be taken to ensure |
| * there are no other threads accessing the cache key. |
| * This is used to clean up cached clones of queries. |
| */ |
| public void clearQueryCache(ReadQuery query) { |
| if (query != null) {// PERF: use query name, unless no name. |
| Object queryKey = query.getName(); |
| if ((queryKey == null) || ((String)queryKey).length() == 0) { |
| queryKey = query; |
| } |
| this.queryResults.remove(queryKey); |
| } |
| } |
| |
| /** |
| * Invalidate/remove any results for the class from the query cache. |
| * This is used to invalidate the query cache on any change. |
| */ |
| public void invalidateQueryCache(Class classThatChanged) { |
| if (this.queryResultsInvalidationsByClass == null) { |
| return; |
| } |
| Set invalidations = this.queryResultsInvalidationsByClass.get(classThatChanged); |
| if (invalidations != null) { |
| for (Object queryKey : invalidations) { |
| this.queryResults.remove(queryKey); |
| } |
| } |
| Class superClass = classThatChanged.getSuperclass(); |
| if ((superClass != null) && (superClass != ClassConstants.OBJECT)) { |
| invalidateQueryCache(superClass); |
| } |
| } |
| |
| /** |
| * Return true if an CacheKey with the primary key is in the map. |
| * User API. |
| * @param key is the primary key for the object to search for. |
| */ |
| public boolean containsKey(Object key, Class theClass, ClassDescriptor descriptor) { |
| if (key == null) { |
| return false; |
| } |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return false; |
| } |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| return map.containsKey(key); |
| } finally { |
| releaseReadLock(); |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| } else { |
| return map.containsKey(key); |
| } |
| } |
| |
| /** |
| * Query the cache in-memory. |
| */ |
| public Vector getAllFromIdentityMap(Expression selectionCriteria, Class theClass, Record translationRow, int valueHolderPolicy, boolean shouldReturnInvalidatedObjects) { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| Vector objects = null; |
| try { |
| if (selectionCriteria != null) { |
| // PERF: Avoid clone of expression. |
| ExpressionBuilder builder = selectionCriteria.getBuilder(); |
| if (builder != null && builder.getSession() == null) { |
| builder.setSession(this.session.getRootSession(null)); |
| builder.setQueryClass(theClass); |
| } |
| } |
| objects = new Vector(); |
| IdentityMap map = getIdentityMap(descriptor, false); |
| |
| // Bug #522635 - if policy is set to trigger indirection, then iterate over a copy of the cache keys collection |
| // to avoid a ConcurrentModificationException |
| final Enumeration cacheEnum = valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_TRIGGER_INDIRECTION ? map.cloneKeys() : map.keys(); |
| |
| // bug 327900 - If don't read subclasses is set on the descriptor heed it. |
| boolean readSubclassesOrNoInheritance = (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().shouldReadSubclasses()); |
| |
| // cache the current time to avoid calculating it every time through the loop |
| long currentTimeInMillis = System.currentTimeMillis(); |
| while (cacheEnum.hasMoreElements()) { |
| CacheKey key = (CacheKey)cacheEnum.nextElement(); |
| if ((key.getObject() == null) || (!shouldReturnInvalidatedObjects && descriptor.getCacheInvalidationPolicy().isInvalidated(key, currentTimeInMillis))) { |
| continue; |
| } |
| Object object = key.getObject(); |
| |
| // Bug # 3216337 - key.getObject() should check for null; object may be GC'd (MWN) |
| if (object == null) { |
| continue; |
| } |
| |
| // Must check for inheritance. |
| // bug 327900 |
| if ((object.getClass() == theClass) || (readSubclassesOrNoInheritance && (theClass.isInstance(object)))) { |
| if (selectionCriteria == null) { |
| objects.add(object); |
| } else { |
| try { |
| if (selectionCriteria.doesConform(object, this.session, (AbstractRecord)translationRow, valueHolderPolicy)) { |
| objects.add(object); |
| } |
| } catch (QueryException queryException) { |
| if (queryException.getErrorCode() == QueryException.MUST_INSTANTIATE_VALUEHOLDERS) { |
| if (valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_CONFORMED) { |
| objects.add(object); |
| } else if (valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_THROW_INDIRECTION_EXCEPTION) { |
| throw queryException; |
| } |
| } else { |
| throw queryException; |
| } |
| } |
| } |
| } |
| } |
| } finally { |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| return objects; |
| } |
| |
| /** |
| * 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 |
| * @throws QueryException |
| */ |
| public Map<Object, Object> getAllFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session){ |
| return getIdentityMap(descriptor).getAllFromIdentityMapWithEntityPK(pkList, descriptor, 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 |
| * @throws QueryException |
| */ |
| public Map<Object, CacheKey> getAllCacheKeysFromIdentityMapWithEntityPK(Object[] pkList, ClassDescriptor descriptor, AbstractSession session){ |
| return getIdentityMap(descriptor).getAllCacheKeysFromIdentityMapWithEntityPK(pkList, descriptor, session); |
| } |
| |
| /** |
| * Invalidate objects meeting selectionCriteria. |
| */ |
| public void invalidateObjects(Expression selectionCriteria, Class theClass, Record translationRow, boolean shouldInvalidateOnException) { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| try { |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if(map == null) { |
| return; |
| } |
| boolean isChildDescriptor = descriptor.isChildDescriptor(); |
| if (selectionCriteria != null) { |
| // PERF: Avoid clone of expression. |
| ExpressionBuilder builder = selectionCriteria.getBuilder(); |
| if (builder.getSession() == null) { |
| builder.setSession(this.session.getRootSession(null)); |
| builder.setQueryClass(theClass); |
| } |
| CacheInvalidationPolicy cacheInvalidationPolicy = descriptor.getCacheInvalidationPolicy(); |
| int inMemoryQueryIndirectionPolicy = InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_NOT_CONFORMED; |
| if (shouldInvalidateOnException) { |
| inMemoryQueryIndirectionPolicy = InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_CONFORMED; |
| } |
| |
| // cache the current time to avoid calculating it every time through the loop |
| long currentTimeInMillis = System.currentTimeMillis(); |
| //Enumeration doesn't checkReadLocks |
| for (Enumeration cacheEnum = map.keys(false); cacheEnum.hasMoreElements();) { |
| CacheKey key = (CacheKey)cacheEnum.nextElement(); |
| Object object = key.getObject(); |
| if (object == null || cacheInvalidationPolicy.isInvalidated(key, currentTimeInMillis)) { |
| continue; |
| } |
| |
| // Must check for inheritance. |
| if (!isChildDescriptor || (object.getClass() == theClass) || (theClass.isInstance(object))) { |
| try { |
| if (selectionCriteria.doesConform(object, this.session, (AbstractRecord)translationRow, inMemoryQueryIndirectionPolicy)) { |
| key.setInvalidationState(CacheKey.CACHE_KEY_INVALID); |
| } |
| } catch (QueryException queryException) { |
| if(queryException.getErrorCode() == QueryException.CANNOT_CONFORM_EXPRESSION) { |
| // if the expression can't be confirmed for the object it's likely the same will happen to all other objects - |
| // invalidate all objects of theClass if required, otherwise leave. |
| if (shouldInvalidateOnException) { |
| invalidateObjects(null, theClass, null, true); |
| } else { |
| return; |
| } |
| } else { |
| if (shouldInvalidateOnException) { |
| key.setInvalidationState(CacheKey.CACHE_KEY_INVALID); |
| } |
| } |
| } catch (RuntimeException runtimeException) { |
| if (shouldInvalidateOnException) { |
| key.setInvalidationState(CacheKey.CACHE_KEY_INVALID); |
| } |
| } |
| } |
| } |
| } else { |
| // selectionCriteria == null |
| if(isChildDescriptor) { |
| // Must check for inheritance. |
| for (Enumeration cacheEnum = map.keys(false); cacheEnum.hasMoreElements();) { |
| CacheKey key = (CacheKey)cacheEnum.nextElement(); |
| Object object = key.getObject(); |
| if (object == null) { |
| continue; |
| } |
| |
| if ((object.getClass() == theClass) || (theClass.isInstance(object))) { |
| key.setInvalidationState(CacheKey.CACHE_KEY_INVALID); |
| } |
| } |
| } else { |
| // if it's either a root class or there is no inheritance just invalidate the whole identity map |
| for (Enumeration cacheEnum = map.keys(false); cacheEnum.hasMoreElements();) { |
| CacheKey key = (CacheKey)cacheEnum.nextElement(); |
| key.setInvalidationState(CacheKey.CACHE_KEY_INVALID); |
| } |
| } |
| } |
| invalidateQueryCache(theClass); |
| } finally { |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| } |
| |
| /** |
| * Retrieve the cache key for the given identity information. |
| */ |
| public CacheKey getCacheKeyForObjectForLock(Object primaryKey, Class theClass, ClassDescriptor descriptor) { |
| if (primaryKey == null) { |
| return null; |
| } |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return null; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = map.getCacheKeyForLock(primaryKey); |
| } finally { |
| releaseReadLock(); |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| } else { |
| cacheKey = map.getCacheKeyForLock(primaryKey); |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Retrieve the cache key for the given identity information. |
| */ |
| public CacheKey getCacheKeyForObject(Object primaryKey, Class theClass, ClassDescriptor descriptor, boolean forMerge) { |
| if (primaryKey == null) { |
| return null; |
| } |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return null; |
| } |
| CacheKey cacheKey = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = map.getCacheKey(primaryKey, forMerge); |
| } finally { |
| releaseReadLock(); |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| } else { |
| cacheKey = map.getCacheKey(primaryKey, forMerge); |
| } |
| return cacheKey; |
| } |
| /** |
| * Return the cache mutex. |
| * This allows for the entire cache to be locked. |
| * This is done for transaction isolations on merges, although never locked by default. |
| */ |
| public ConcurrencyManager getCacheMutex() { |
| return cacheMutex; |
| } |
| |
| /** |
| * This method is used to get a list of those classes with IdentityMaps in the Session. |
| */ |
| public Vector getClassesRegistered() { |
| Iterator classes = getIdentityMaps().keySet().iterator(); |
| Vector results = new Vector(getIdentityMaps().size()); |
| while (classes.hasNext()) { |
| results.add(((Class)classes.next()).getName()); |
| } |
| return results; |
| } |
| |
| /** |
| * Get the object from the identity map which has the same identity information |
| * as the given object. |
| */ |
| public Object getFromIdentityMap(Object object) { |
| ClassDescriptor descriptor = this.session.getDescriptor(object); |
| Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this.session); |
| return getFromIdentityMap(primaryKey, object.getClass(), descriptor); |
| } |
| |
| /** |
| * Get the object from the identity map which has the given primary key and class. |
| */ |
| public Object getFromIdentityMap(Object key, Class theClass, ClassDescriptor descriptor) { |
| return getFromIdentityMap(key, theClass, true, descriptor); |
| } |
| |
| /** |
| * Get the object from the identity map which has the given primary key and class. |
| * Only return the object if it has not been invalidated. |
| */ |
| public Object getFromIdentityMap(Object key, Class theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| if (key == null) { |
| return null; |
| } |
| |
| CacheKey cacheKey; |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return null; |
| } |
| Object domainObject = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = map.getCacheKey(key, false); |
| } finally { |
| releaseReadLock(); |
| } |
| } else { |
| cacheKey = map.getCacheKey(key, false); |
| } |
| |
| if ((cacheKey != null) && (shouldReturnInvalidatedObjects || !descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey))) { |
| // BUG#4772232 - The read-lock must be checked to avoid returning a partial object, |
| // PERF: Just check the read-lock to avoid acquire if not locked. |
| // This is ok if you get the object first, as the object cannot gc and identity is always maintained. |
| domainObject = cacheKey.getObject(); |
| cacheKey.checkReadLock(); |
| // Resolve the inheritance issues. |
| domainObject = checkForInheritance(domainObject, theClass, descriptor); |
| } |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| |
| return domainObject; |
| } |
| |
| public Object getFromIdentityMap(Expression selectionCriteria, Class theClass, Record translationRow, int valueHolderPolicy, boolean conforming, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| UnitOfWorkImpl unitOfWork = (conforming) ? (UnitOfWorkImpl)this.session : null; |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| try { |
| if (selectionCriteria != null) { |
| // PERF: Avoid clone of expression. |
| ExpressionBuilder builder = selectionCriteria.getBuilder(); |
| if (builder.getSession() == null) { |
| builder.setSession(this.session.getRootSession(null)); |
| builder.setQueryClass(theClass); |
| } |
| } |
| IdentityMap map = getIdentityMap(descriptor, false); |
| |
| // Bug #321041 - if policy is set to trigger indirection, then iterate over a copy of the cache keys collection |
| // to avoid a ConcurrentModificationException |
| Enumeration cacheEnum = valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_TRIGGER_INDIRECTION ? map.cloneKeys() : map.keys(); |
| |
| // cache the current time to avoid calculating it every time through the loop |
| long currentTimeInMillis = System.currentTimeMillis(); |
| while (cacheEnum.hasMoreElements()) { |
| CacheKey key = (CacheKey)cacheEnum.nextElement(); |
| if (!shouldReturnInvalidatedObjects && descriptor.getCacheInvalidationPolicy().isInvalidated(key, currentTimeInMillis)) { |
| continue; |
| } |
| Object object = key.getObject(); |
| |
| // Bug # 3216337 - key.getObject() should check for null; object may be GC'd (MWN) |
| if (object == null) { |
| continue; |
| } |
| |
| // Must check for inheritance. |
| if ((object.getClass() == theClass) || (theClass.isInstance(object))) { |
| if (selectionCriteria == null) { |
| // bug 2782991: if first found was deleted nothing returned. |
| if (!(conforming && unitOfWork.isObjectDeleted(object))) { |
| return object; |
| } |
| } |
| |
| //CR 3677 integration of a ValueHolderPolicy |
| try { |
| if (selectionCriteria.doesConform(object, this.session, (AbstractRecord)translationRow, valueHolderPolicy)) { |
| // bug 2782991: if first found was deleted nothing returned. |
| if (!(conforming && unitOfWork.isObjectDeleted(object))) { |
| return object; |
| } |
| } |
| } catch (QueryException queryException) { |
| if (queryException.getErrorCode() == QueryException.MUST_INSTANTIATE_VALUEHOLDERS) { |
| if (valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_CONFORMED) { |
| // bug 2782991: if first found was deleted nothing returned. |
| if (!(conforming && unitOfWork.isObjectDeleted(object))) { |
| return object; |
| } |
| } else if (valueHolderPolicy == InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_NOT_CONFORMED) { |
| // For bug 2667870 just skip this item, but do not abort. |
| } else { |
| throw queryException; |
| } |
| } else { |
| throw queryException; |
| } |
| } |
| } |
| } |
| } finally { |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| return null; |
| } |
| |
| /** |
| * Get the object from the cache with the given primary key and class. |
| * Do not return the object if it was invalidated. |
| */ |
| public Object getFromIdentityMapWithDeferredLock(Object key, Class theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| if (key == null) { |
| return null; |
| } |
| |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return null; |
| } |
| CacheKey cacheKey; |
| Object domainObject = null; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| cacheKey = map.getCacheKey(key, false); |
| } finally { |
| releaseReadLock(); |
| } |
| } else { |
| cacheKey = map.getCacheKey(key, false); |
| } |
| |
| if ((cacheKey != null) && (shouldReturnInvalidatedObjects || !descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey))) { |
| // PERF: Just check the read-lock to avoid acquire if not locked. |
| // This is ok if you get the object first, as the object cannot gc and identity is always maintained. |
| domainObject = cacheKey.getObject(); |
| cacheKey.checkDeferredLock(); |
| // Reslove inheritance issues. |
| domainObject = checkForInheritance(domainObject, theClass, descriptor); |
| } |
| |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } |
| |
| return domainObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the identity map for the class, if missing create a new one. |
| */ |
| public IdentityMap getIdentityMap(ClassDescriptor descriptor) { |
| return getIdentityMap(descriptor, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the identity map for the class. |
| * @param returnNullIfNoMap if true return null if no map, otherwise create one. |
| */ |
| public IdentityMap getIdentityMap(ClassDescriptor descriptor, boolean returnNullIfNoMap) { |
| // Ensure that an identitymap is only used for the root descriptor for inheritance. |
| // This is required to obtain proper cache hits. |
| if (descriptor.hasInheritance()) { |
| descriptor = descriptor.getInheritancePolicy().getRootParentDescriptor(); |
| } |
| Class descriptorClass = descriptor.getJavaClass(); |
| |
| // PERF: First check if same as lastAccessedIdentityMap to avoid lookup. |
| IdentityMap tempMap = this.lastAccessedIdentityMap; |
| if ((tempMap != null) && (tempMap.getDescriptorClass() == descriptorClass)) { |
| return tempMap; |
| } |
| |
| // PERF: Avoid synchronization through get and putIfAbsent double-check. |
| IdentityMap identityMap = this.identityMaps.get(descriptorClass); |
| if (identityMap == null) { |
| if (returnNullIfNoMap && descriptor.getCachePolicy().getCacheInterceptorClass() == null) { |
| //interceptor monitors the identity map and needs to know that a request occurred. |
| return null; |
| } |
| IdentityMap newIdentityMap = null; |
| if (this.session.isUnitOfWork() || this.session.isIsolatedClientSession()) { |
| newIdentityMap = buildNewIdentityMap(descriptor); |
| identityMap = this.identityMaps.put(descriptorClass, newIdentityMap); |
| } else { |
| newIdentityMap = buildNewIdentityMap(descriptor); |
| identityMap = (IdentityMap)((ConcurrentMap)this.identityMaps).putIfAbsent(descriptorClass, newIdentityMap); |
| } |
| if (identityMap == null) { |
| identityMap = newIdentityMap; |
| } |
| } |
| this.lastAccessedIdentityMap = identityMap; |
| |
| return identityMap; |
| } |
| |
| protected Map<Class, IdentityMap> getIdentityMaps() { |
| return identityMaps; |
| } |
| |
| /** |
| * Return an iterator of the classes in the identity map. |
| */ |
| public Iterator getIdentityMapClasses() { |
| return getIdentityMaps().keySet().iterator(); |
| } |
| |
| /** |
| * Get the cached results associated with a query. Results are cached by the |
| * values of the parameters to the query so different parameters will have |
| * different cached results. |
| */ |
| public Object getQueryResult(ReadQuery query, List parameters, boolean shouldCheckExpiry) { |
| if (query.getQueryResultsCachePolicy() == null) { |
| return null; |
| } |
| // PERF: use query name, unless no name. |
| Object queryKey = query.getName(); |
| if ((queryKey == null) || ((String)queryKey).length() == 0) { |
| queryKey = query; |
| } |
| IdentityMap map = this.queryResults.get(queryKey); |
| if (map == null) { |
| return null; |
| } |
| |
| Object lookupParameters; |
| if ((parameters == null) || parameters.isEmpty()) { |
| lookupParameters = CacheId.EMPTY; |
| } else { |
| lookupParameters = new CacheId(parameters.toArray()); |
| } |
| |
| CacheKey key = map.getCacheKey(lookupParameters, false); |
| if ((key == null) || (shouldCheckExpiry && query.getQueryResultsCachePolicy().getCacheInvalidationPolicy().isInvalidated(key))) { |
| return null; |
| } |
| return key.getObject(); |
| } |
| |
| /** |
| * Return the cache key for the cache index or null if not found. |
| */ |
| public CacheKey getCacheKeyByIndex(CacheIndex index, CacheId indexValues, boolean shouldCheckExpiry, ClassDescriptor descriptor) { |
| if (this.cacheIndexes == null) { |
| return null; |
| } |
| IdentityMap map = this.cacheIndexes.get(index); |
| if (map == null) { |
| return null; |
| } |
| |
| CacheKey cacheKey = map.getCacheKey(indexValues, false); |
| if (cacheKey == null) { |
| return null; |
| } |
| // The cache key is nested as the object cache key is put into the cache. |
| cacheKey = (CacheKey)cacheKey.getObject(); |
| if (cacheKey == null) { |
| return null; |
| } |
| if (shouldCheckExpiry && descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey)) { |
| return null; |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Index the cache key by the index values. |
| */ |
| public void putCacheKeyByIndex(CacheIndex index, CacheId indexValues, CacheKey cacheKey, ClassDescriptor descriptor) { |
| if (this.cacheIndexes == null) { |
| return; |
| } |
| if (indexValues == null) { |
| return; |
| } |
| IdentityMap map = this.cacheIndexes.get(index); |
| if (map == null) { |
| synchronized (this.cacheIndexes) { |
| map = this.cacheIndexes.get(index); |
| if (map == null) { |
| map = buildNewIdentityMap(index.getCacheType(), index.getCacheSize(), null, false); |
| this.cacheIndexes.put(index, map); |
| } |
| } |
| } |
| map.put(indexValues, cacheKey, null, 0); |
| } |
| |
| protected AbstractSession getSession() { |
| return session; |
| } |
| |
| /** |
| * Get the wrapper object from the cache key associated with the given primary key, |
| * this is used for EJB. |
| */ |
| public Object getWrapper(Object primaryKey, Class theClass) { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| IdentityMap map = getIdentityMap(descriptor, false); |
| Object wrapper; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| wrapper = map.getWrapper(primaryKey); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| wrapper = map.getWrapper(primaryKey); |
| } |
| return wrapper; |
| } |
| |
| /** |
| * Returns the single write Lock manager for this session |
| */ |
| public WriteLockManager getWriteLockManager() { |
| // With Isolated Sessions not all Identity maps need a WriteLockManager so |
| //lazy initialize |
| synchronized (this) { |
| if (this.writeLockManager == null) { |
| this.writeLockManager = new WriteLockManager(); |
| } |
| } |
| return this.writeLockManager; |
| } |
| |
| /** |
| * Retrieve the write lock value of the cache key associated with the given primary key, |
| */ |
| public Object getWriteLockValue(Object primaryKey, Class domainClass, ClassDescriptor descriptor) { |
| if (primaryKey == null) { |
| return null; |
| } |
| IdentityMap map = getIdentityMap(descriptor, false); |
| Object value; |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| acquireReadLock(); |
| try { |
| value = map.getWriteLockValue(primaryKey); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| value = map.getWriteLockValue(primaryKey); |
| } |
| return value; |
| } |
| |
| /** |
| * Reset the identity map for only the instances of the class. |
| * For inheritance the user must make sure that they only use the root class. |
| */ |
| public void initializeIdentityMap(Class theClass) throws EclipseLinkException { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| |
| if (descriptor == null) { |
| throw ValidationException.missingDescriptor(String.valueOf(theClass)); |
| } |
| if (descriptor.isChildDescriptor()) { |
| throw ValidationException.childDescriptorsDoNotHaveIdentityMap(); |
| } |
| // Bug 3736313 - look up identity map by descriptor's java class |
| Class javaClass = descriptor.getJavaClass(); |
| IdentityMap identityMap = buildNewIdentityMap(descriptor); |
| getIdentityMaps().put(javaClass, identityMap); |
| clearLastAccessedIdentityMap(); |
| invalidateQueryCache(theClass); |
| } |
| |
| public void initializeIdentityMaps() { |
| clearLastAccessedIdentityMap(); |
| setIdentityMaps(new ConcurrentHashMap()); |
| clearQueryCache(); |
| clearCacheIndexes(); |
| } |
| |
| /** |
| * Used to print all the objects in the identity map of the passed in class. |
| * The output of this method will be logged to this session's SessionLog at SEVERE level. |
| */ |
| public void printIdentityMap(Class businessClass) { |
| String cr = Helper.cr(); |
| ClassDescriptor descriptor = this.session.getDescriptor(businessClass); |
| int cacheCounter = 0; |
| StringWriter writer = new StringWriter(); |
| if (descriptor.isDescriptorTypeAggregate()) { |
| return; //do nothing if descriptor is aggregate |
| } |
| |
| IdentityMap map = getIdentityMap(descriptor, true); |
| if (map == null) { |
| return; |
| } |
| writer.write(LoggingLocalization.buildMessage("identitymap_for", new Object[] { cr, Helper.getShortClassName(map.getClass()), Helper.getShortClassName(businessClass) })); |
| if (descriptor.hasInheritance()) { |
| if (descriptor.getInheritancePolicy().isRootParentDescriptor()) { |
| writer.write(LoggingLocalization.buildMessage("includes")); |
| List<ClassDescriptor> childDescriptors = descriptor.getInheritancePolicy().getChildDescriptors(); |
| if ((childDescriptors != null) && (childDescriptors.size() != 0)) {//Bug#2675242 |
| Iterator<ClassDescriptor> iterator = childDescriptors.iterator(); |
| writer.write(Helper.getShortClassName(iterator.next().getJavaClass())); |
| while (iterator.hasNext()) { |
| writer.write(", " + Helper.getShortClassName(iterator.next().getJavaClass())); |
| } |
| } |
| writer.write(")"); |
| } |
| } |
| |
| for (Enumeration enumtr = map.keys(); enumtr.hasMoreElements();) { |
| org.eclipse.persistence.internal.identitymaps.CacheKey cacheKey = (org.eclipse.persistence.internal.identitymaps.CacheKey)enumtr.nextElement(); |
| Object object = cacheKey.getObject(); |
| if (businessClass.isInstance(object)) { |
| cacheCounter++; |
| Object key = cacheKey.getKey(); |
| if (object == null) { |
| writer.write(LoggingLocalization.buildMessage("key_object_null", new Object[] { cr, key, "\t" })); |
| } else { |
| String hashCode = String.valueOf(System.identityHashCode(object)); |
| if (descriptor.usesOptimisticLocking() && descriptor.usesVersionLocking()) { |
| // Obtain writeLockValue and convert the value to String |
| Object writeLockValue = descriptor.getOptimisticLockingPolicy().getWriteLockValue(object, key, session); |
| String version = (String) session.getPlatform().convertObject(writeLockValue, String.class); |
| writer.write(LoggingLocalization.buildMessage("key_version_identity_hash_code_object", new Object[] { cr, key, "\t", hashCode, object, version })); |
| } else { |
| writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, key, "\t", hashCode, object })); |
| } |
| } |
| } |
| } |
| writer.write(LoggingLocalization.buildMessage("elements", new Object[] { cr, String.valueOf(cacheCounter) })); |
| this.session.log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), null, null, false); |
| } |
| |
| /** |
| * Used to print all the objects in every identity map in this session. |
| * The output of this method will be logged to this session's SessionLog at SEVERE level. |
| */ |
| public void printIdentityMaps() { |
| for (Iterator iterator = this.session.getDescriptors().keySet().iterator(); |
| iterator.hasNext();) { |
| Class businessClass = (Class)iterator.next(); |
| ClassDescriptor descriptor = this.session.getDescriptor(businessClass); |
| if (descriptor.hasInheritance()) { |
| if (descriptor.getInheritancePolicy().isRootParentDescriptor()) { |
| printIdentityMap(businessClass); |
| } |
| } else { |
| printIdentityMap(businessClass); |
| } |
| } |
| } |
| |
| /** |
| * Used to print all the Locks in every identity map in this session. |
| * The output of this method will be logged to this session's SessionLog at FINEST level. |
| */ |
| public void printLocks() { |
| StringWriter writer = new StringWriter(); |
| HashMap threadCollection = new HashMap(); |
| writer.write(TraceLocalization.buildMessage("lock_writer_header", null) + Helper.cr()); |
| Iterator idenityMapsIterator = this.session.getIdentityMapAccessorInstance().getIdentityMapManager().getIdentityMaps().values().iterator(); |
| while (idenityMapsIterator.hasNext()) { |
| IdentityMap idenityMap = (IdentityMap)idenityMapsIterator.next(); |
| idenityMap.collectLocks(threadCollection); |
| } |
| Object[] parameters = new Object[1]; |
| for (Iterator threads = threadCollection.keySet().iterator(); threads.hasNext();) { |
| Thread activeThread = (Thread)threads.next(); |
| parameters[0] = activeThread.getName(); |
| writer.write(TraceLocalization.buildMessage("active_thread", parameters) + Helper.cr()); |
| for (Iterator cacheKeys = ((HashSet)threadCollection.get(activeThread)).iterator(); |
| cacheKeys.hasNext();) { |
| CacheKey cacheKey = (CacheKey)cacheKeys.next(); |
| if (cacheKey.isAcquired() && cacheKey.getActiveThread() == activeThread){ |
| parameters[0] = cacheKey.getObject(); |
| writer.write(TraceLocalization.buildMessage("locked_object", parameters) + Helper.cr()); |
| writer.write("PK: " + cacheKey.getKey() + Helper.cr()); |
| parameters[0] = cacheKey.getDepth(); |
| writer.write(TraceLocalization.buildMessage("depth", parameters) + Helper.cr()); |
| Exception stack = cacheKey.getStack(); |
| if (stack != null) stack.printStackTrace(new PrintWriter(writer)); |
| } else{ |
| writer.write(TraceLocalization.buildMessage("cachekey_released", new Object[]{})); |
| parameters[0] = cacheKey.getObject(); |
| writer.write(TraceLocalization.buildMessage("locked_object", parameters) + Helper.cr()); |
| writer.write("PK: " + cacheKey.getKey() + Helper.cr()); |
| } |
| } |
| DeferredLockManager deferredLockManager = ConcurrencyManager.getDeferredLockManager(activeThread); |
| if (deferredLockManager != null) { |
| for (Iterator deferredLocks = deferredLockManager.getDeferredLocks().iterator(); |
| deferredLocks.hasNext();) { |
| ConcurrencyManager lock = (ConcurrencyManager)deferredLocks.next(); |
| if (lock instanceof CacheKey){ |
| parameters[0] = ((CacheKey)lock).getObject(); |
| writer.write(TraceLocalization.buildMessage("deferred_locks", parameters) + Helper.cr()); |
| } |
| } |
| } |
| } |
| writer.write(Helper.cr() + TraceLocalization.buildMessage("lock_writer_footer", null) + Helper.cr()); |
| this.session.log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), null, null, false); |
| } |
| |
| /** |
| * Used to print all the Locks in the specified identity map in this session. |
| * The output of this method will be logged to this session's SessionLog at FINEST level. |
| */ |
| public void printLocks(Class theClass) { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| StringWriter writer = new StringWriter(); |
| HashMap threadCollection = new HashMap(); |
| writer.write(TraceLocalization.buildMessage("lock_writer_header", null) + Helper.cr()); |
| IdentityMap identityMap = getIdentityMap(descriptor, false); |
| identityMap.collectLocks(threadCollection); |
| |
| Object[] parameters = new Object[1]; |
| for (Iterator threads = threadCollection.keySet().iterator(); threads.hasNext();) { |
| Thread activeThread = (Thread)threads.next(); |
| parameters[0] = activeThread.getName(); |
| writer.write(TraceLocalization.buildMessage("active_thread", parameters) + Helper.cr()); |
| for (Iterator cacheKeys = ((HashSet)threadCollection.get(activeThread)).iterator(); |
| cacheKeys.hasNext();) { |
| CacheKey cacheKey = (CacheKey)cacheKeys.next(); |
| parameters[0] = cacheKey.getObject(); |
| writer.write(TraceLocalization.buildMessage("locked_object", parameters) + Helper.cr()); |
| parameters[0] = cacheKey.getDepth(); |
| writer.write(TraceLocalization.buildMessage("depth", parameters) + Helper.cr()); |
| } |
| DeferredLockManager deferredLockManager = ConcurrencyManager.getDeferredLockManager(activeThread); |
| if (deferredLockManager != null) { |
| for (Iterator deferredLocks = deferredLockManager.getDeferredLocks().iterator(); |
| deferredLocks.hasNext();) { |
| ConcurrencyManager lock = (ConcurrencyManager)deferredLocks.next(); |
| if (lock instanceof CacheKey){ |
| parameters[0] = ((CacheKey)lock).getObject(); |
| writer.write(TraceLocalization.buildMessage("deferred_locks", parameters) + Helper.cr()); |
| } |
| } |
| } |
| } |
| writer.write(Helper.cr() + TraceLocalization.buildMessage("lock_writer_footer", null) + Helper.cr()); |
| this.session.log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), null, null, false); |
| } |
| |
| /** |
| * Register the object with the identity map. |
| * The object must always be registered with its version number if optimistic locking is used. |
| * The readTime may also be included in the cache key as it is constructed |
| */ |
| public CacheKey putInIdentityMap(Object domainObject, Object keys, Object writeLockValue, long readTime, ClassDescriptor descriptor) { |
| if (keys == null) { |
| return null; |
| } |
| ObjectBuilder builder = descriptor.getObjectBuilder(); |
| Object implementation = builder.unwrapObject(domainObject, this.session); |
| |
| IdentityMap map = getIdentityMap(descriptor, false); |
| CacheKey cacheKey; |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| // This is atomic so considered a read lock. |
| acquireReadLock(); |
| try { |
| cacheKey = map.put(keys, implementation, writeLockValue, readTime); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| cacheKey = map.put(keys, implementation, writeLockValue, readTime); |
| } |
| return cacheKey; |
| } |
| |
| /** |
| * Set the results for a query. |
| * Query results are cached based on the parameter values provided to the query |
| * different parameter values access different caches. |
| */ |
| public void putQueryResult(ReadQuery query, List parameters, Object results) { |
| if ((results == null) || (results == InvalidObject.instance())) { |
| if (query.getQueryResultsCachePolicy().isNullIgnored()) { |
| return; |
| } |
| } |
| // PERF: use query name, unless no name. |
| Object queryKey = query.getName(); |
| if ((queryKey == null) || ((String)queryKey).length() == 0) { |
| queryKey = query; |
| } |
| IdentityMap map = this.queryResults.get(queryKey); |
| if (map == null) { |
| synchronized (this.queryResults) { |
| map = this.queryResults.get(queryKey); |
| if (map == null) { |
| int size = query.getQueryResultsCachePolicy().getMaximumCachedResults(); |
| // PERF: If no parameters, then there can only be one result. |
| if ((parameters == null) || parameters.isEmpty()) { |
| size = 1; |
| } |
| map = buildNewIdentityMap(query.getQueryResultsCachePolicy().getCacheType(), size, null, false); |
| this.queryResults.put(queryKey, map); |
| // Mark the query to be invalidated for the query classes. |
| if (query.getQueryResultsCachePolicy().getInvalidateOnChange()) { |
| for (Class queryClass : query.getQueryResultsCachePolicy().getInvalidationClasses()) { |
| Set invalidations = this.queryResultsInvalidationsByClass.get(queryClass); |
| if (invalidations == null) { |
| invalidations = new HashSet(); |
| this.queryResultsInvalidationsByClass.put(queryClass, invalidations); |
| } |
| invalidations.add(queryKey); |
| } |
| } |
| } |
| } |
| } |
| Object lookupParameters; |
| if ((parameters == null) || parameters.isEmpty()) { |
| lookupParameters = CacheId.EMPTY; |
| } else { |
| lookupParameters = new CacheId(parameters.toArray()); |
| } |
| long queryTime = 0; |
| if (query.isObjectLevelReadQuery()) { |
| queryTime = ((ObjectLevelReadQuery)query).getExecutionTime(); |
| } |
| if (queryTime == 0) { |
| queryTime = System.currentTimeMillis(); |
| } |
| // Bug 6138532 - store InvalidObject for "no results", do not store null |
| if (results == null) { |
| results = InvalidObject.instance(); |
| } |
| map.put(lookupParameters, results, null, queryTime); |
| } |
| |
| /** |
| * Read-release the local-map and the entire cache. |
| */ |
| protected void releaseReadLock() { |
| if (this.session.getDatasourceLogin().shouldSynchronizedReadOnWrite()) { |
| getCacheMutex().releaseReadLock(); |
| } |
| } |
| |
| /** |
| * Lock the entire cache if the cache isolation requires. |
| * By default concurrent reads and writes are allowed. |
| * By write, unit of work merge is meant. |
| */ |
| public void releaseWriteLock() { |
| if (this.session.getDatasourceLogin().shouldSynchronizedReadOnWrite() || this.session.getDatasourceLogin().shouldSynchronizeWrites()) { |
| getCacheMutex().release(); |
| } |
| } |
| |
| /** |
| * Remove the object from the object cache. |
| */ |
| public Object removeFromIdentityMap(Object key, Class domainClass, ClassDescriptor descriptor, Object objectToRemove) { |
| if (key == null) { |
| return null; |
| } |
| IdentityMap map = getIdentityMap(descriptor, false); |
| Object value; |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| // This is atomic so considered a read lock. |
| acquireReadLock(); |
| try { |
| value = map.remove(key, objectToRemove); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| value = map.remove(key, objectToRemove); |
| } |
| if (session.getProject().allowExtendedCacheLogging()) { |
| session.log(SessionLog.FINEST, SessionLog.CACHE, "cache_item_removal", new Object[] {domainClass, key, Thread.currentThread().getId(), Thread.currentThread().getName()}); |
| } |
| return value; |
| } |
| |
| /** |
| * Set the cache mutex. |
| * This allows for the entire cache to be locked. |
| * This is done for transaction isolations on merges, although never locked by default. |
| */ |
| protected void setCacheMutex(ConcurrencyManager cacheMutex) { |
| this.cacheMutex = cacheMutex; |
| } |
| |
| public void setIdentityMaps(ConcurrentMap identityMaps) { |
| clearLastAccessedIdentityMap(); |
| this.identityMaps = identityMaps; |
| } |
| |
| protected void setSession(AbstractSession session) { |
| this.session = session; |
| } |
| |
| /** |
| * Update the wrapper object the cache key associated with the given primary key, |
| * this is used for EJB. |
| */ |
| public void setWrapper(Object primaryKey, Class theClass, Object wrapper) { |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| IdentityMap map = getIdentityMap(descriptor, false); |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| // This is atomic so considered a read lock. |
| acquireReadLock(); |
| try { |
| map.setWrapper(primaryKey, wrapper); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| map.setWrapper(primaryKey, wrapper); |
| } |
| } |
| |
| /** |
| * Update the write lock value of the cache key associated with the given primary key, |
| */ |
| public void setWriteLockValue(Object primaryKey, Class theClass, Object writeLockValue) { |
| if (primaryKey == null) { |
| return; |
| } |
| ClassDescriptor descriptor = this.session.getDescriptor(theClass); |
| IdentityMap map = getIdentityMap(descriptor, false); |
| |
| if (this.isCacheAccessPreCheckRequired) { |
| this.session.startOperationProfile(SessionProfiler.Caching); |
| // This is atomic so considered a read lock. |
| acquireReadLock(); |
| try { |
| map.setWriteLockValue(primaryKey, writeLockValue); |
| } finally { |
| releaseReadLock(); |
| } |
| this.session.endOperationProfile(SessionProfiler.Caching); |
| } else { |
| map.setWriteLockValue(primaryKey, writeLockValue); |
| } |
| } |
| |
| /** |
| * This method is used to resolve the inheritance issues arisen when conforming from the identity map |
| * 1. Avoid reading the unintended subclass during in-memory query(e.g. when querying on large project, do not want |
| * to check small project, both are inherited from the project, and stored in the same identity map). |
| * 2. EJB container-generated classes broke the inheritance hierarchy. Need to use associated descriptor to track |
| * the relationship. CR4005-2612426, King-Sept-18-2002 |
| */ |
| protected Object checkForInheritance(Object domainObject, Class superClass, ClassDescriptor descriptor) { |
| if ((domainObject != null) && ((domainObject.getClass() != superClass) && (!superClass.isInstance(domainObject)))) { |
| // Before returning null, check if we are using EJB inheritance. |
| if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().getUseDescriptorsToValidateInheritedObjects()) { |
| // EJB inheritance on the descriptors, not the container-generated classes/objects. We need to check the |
| // identity map for the bean instance through the descriptor. |
| if (descriptor.getInheritancePolicy().getSubclassDescriptor(domainObject.getClass()) == null) { |
| return null; |
| } |
| return domainObject; |
| } |
| return null; |
| } |
| return domainObject; |
| } |
| } |