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

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

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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.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.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;
import org.eclipse.persistence.sessions.interceptors.CacheInterceptor;

/**
 * 

Purpose: Maintain identity maps for domain classes mapped with EclipseLink. *

Responsibilities:

    *
  • Build new identity maps lazily using info from the descriptor *
  • Insert objects into appropriate identity map *
  • Get object from appropriate identity map using object or primary key with class *
  • Get and Set write lock values for cached objects *
* @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 identityMaps; /** A table of identity maps with the key being the query */ protected Map queryResults; /** A map of class to list of queries that need to be invalidated when that class changes. */ protected Map queryResultsInvalidationsByClass; /** A map of indexes on the cache. */ protected Map 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( final Class identityMapClass, final int size, final ClassDescriptor descriptor, final 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); } } final Class[] parameters = new Class[]{ClassConstants.PINT, ClassDescriptor.class, AbstractSession.class, boolean.class}; final Object[] values = new Object[]{size, descriptor, this.session, isIsolated}; IdentityMap map = PrivilegedAccessHelper.callDoPrivilegedWithException( () -> { final Constructor constructor = PrivilegedAccessHelper.getConstructorFor(identityMapClass, parameters, false); return PrivilegedAccessHelper.invokeConstructor(constructor, values); }, (ex) -> DescriptorException.invalidIdentityMap(descriptor, ex) ); if ((descriptor != null) && (descriptor.getCacheInterceptorClass() != null)) { final Object params[] = new Object[]{map, this.session}; map = PrivilegedAccessHelper.callDoPrivilegedWithException( () -> { final Constructor interceptor = PrivilegedAccessHelper.getConstructorFor( descriptor.getCacheInterceptorClass(), new Class[]{IdentityMap.class, AbstractSession.class}, false); return PrivilegedAccessHelper.invokeConstructor(interceptor, params); }, (ex) -> DescriptorException.invalidIdentityMap(descriptor, ex) ); } return map; } /** * 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 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 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 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 childDescriptors = descriptor.getInheritancePolicy().getChildDescriptors(); if ((childDescriptors != null) && (childDescriptors.size() != 0)) {//Bug#2675242 Iterator 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] = Integer.valueOf(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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy