| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.internal.sessions; |
| |
| import java.util.*; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.internal.descriptors.PersistenceEntity; |
| import org.eclipse.persistence.internal.identitymaps.*; |
| import org.eclipse.persistence.expressions.*; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.queries.*; |
| import org.eclipse.persistence.sessions.DataRecord; |
| |
| /** |
| * INTERNAL: |
| * IdentityMapAccessor subclass for UnitOfWork |
| * Overrides some initialization functionality and some behavior having to do with |
| * getting objects from identity maps. |
| */ |
| public class UnitOfWorkIdentityMapAccessor extends IdentityMapAccessor { |
| |
| public UnitOfWorkIdentityMapAccessor() { |
| } |
| |
| public UnitOfWorkIdentityMapAccessor(AbstractSession session, IdentityMapManager identityMapManager) { |
| super(session, identityMapManager); |
| } |
| |
| /** |
| * ADVANCED: |
| * Clear all the query caches |
| */ |
| @Override |
| public void clearQueryCache() { |
| this.session.getParent().getIdentityMapAccessor().clearQueryCache(); |
| } |
| |
| /** |
| * Invalidate/remove any results for the class from the query cache. |
| * This is used to invalidate the query cache on any change. |
| */ |
| @Override |
| public void invalidateQueryCache(Class<?> classThatChanged) { |
| this.session.getParent().getIdentityMapAccessor().invalidateQueryCache(classThatChanged); |
| } |
| |
| /** |
| * ADVANCED: |
| * Clear the query class associated with the passed-in read query |
| */ |
| @Override |
| public void clearQueryCache(ReadQuery query) { |
| this.session.getParent().getIdentityMapAccessor().clearQueryCache(query); |
| } |
| |
| /** |
| * ADVANCED: |
| * Clear the query cache associated with the named query on the session |
| */ |
| @Override |
| public void clearQueryCache(String sessionQueryName) { |
| this.session.getParent().getIdentityMapAccessor().clearQueryCache((ReadQuery)session.getQuery(sessionQueryName)); |
| } |
| |
| /** |
| * ADVANCED: |
| * Clear the query cache associated with the named query on the descriptor for the given class |
| */ |
| @Override |
| public void clearQueryCache(String descriptorQueryName, Class<?> queryClass) { |
| this.session.getParent().getIdentityMapAccessor().clearQueryCache((ReadQuery)session.getDescriptor(queryClass).getQueryManager().getQuery(descriptorQueryName)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if their is an object for the primary key. |
| */ |
| @Override |
| public boolean containsObjectInIdentityMap(Object primaryKey, Class<?> theClass, ClassDescriptor descriptor) { |
| if (getIdentityMapManager().containsKey(primaryKey, theClass, descriptor)) { |
| return true; |
| } |
| return this.session.getParent().getIdentityMapAccessorInstance().containsObjectInIdentityMap(primaryKey, theClass, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method overrides the getAllFromIdentityMap method in Session. Invalidated Objects |
| * will always be returned from a UnitOfWork. |
| */ |
| @Override |
| public Vector getAllFromIdentityMap(Expression selectionCriteria, Class<?> theClass, DataRecord translationRow, int valueHolderPolicy, boolean shouldReturnInvalidatedObjects) throws QueryException { |
| return super.getAllFromIdentityMap(selectionCriteria, theClass, translationRow, valueHolderPolicy, true); |
| } |
| |
| /** |
| * INTERNAL: |
| * Override the getFromIdentityMapWithDeferredLock method on the session to ensure that |
| * invalidated objects are always returned since this is a UnitOfWork |
| */ |
| @Override |
| public Object getFromIdentityMapWithDeferredLock(Object primaryKey, Class<?> theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| Object objectFromCache = super.getFromIdentityMapWithDeferredLock(primaryKey, theClass, true, descriptor); |
| if (objectFromCache != null){ |
| return objectFromCache; |
| } |
| return getAndCloneCacheKeyFromParent(primaryKey, null, theClass, shouldReturnInvalidatedObjects, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the object from the identity map with the primary key and class. |
| * The parent's cache must be checked after the child's, |
| * if found in the parent, it must be registered/cloned (but must avoid looping). |
| * Note: in a UnitOfWork, invalidated objects will always be returned from the identity map |
| * In the parent session, only return the object if it has not been Invalidated |
| */ |
| @Override |
| public Object getFromIdentityMap(Object primaryKey, Object object, Class<?> theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| Object objectFromCache = super.getFromIdentityMap(primaryKey, object, theClass, true, descriptor); |
| |
| if (objectFromCache != null) { |
| return objectFromCache; |
| } |
| //Bug#4613774 In the parent session, only return the object if it has not been Invalidated |
| return getAndCloneCacheKeyFromParent(primaryKey, object, theClass, shouldReturnInvalidatedObjects, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will return the object from the parent and clone it. |
| */ |
| protected Object getAndCloneCacheKeyFromParent(Object primaryKey, Object objectToClone, Class<?> theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) { |
| // Note: Objects returned from the parent's identity map should include invalidated |
| // objects. This is important because this internal method is used in the existence |
| // check in the UnitOfWork. |
| UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)this.session; |
| CacheKey cacheKey = null; |
| if (objectToClone != null && objectToClone instanceof PersistenceEntity){ |
| cacheKey = ((PersistenceEntity)objectToClone)._persistence_getCacheKey(); |
| } |
| |
| if (cacheKey == null || cacheKey.getOwningMap() == null){ |
| org.eclipse.persistence.internal.sessions.IdentityMapAccessor parentIdentityMapAccessor = unitOfWork.getParentIdentityMapSession(descriptor, false, false).getIdentityMapAccessorInstance(); |
| cacheKey = parentIdentityMapAccessor.getCacheKeyForObject(primaryKey, theClass, descriptor, false); |
| } |
| if ((cacheKey == null) && unitOfWork.getParentIdentityMapSession(descriptor, false, false).isUnitOfWork()) { |
| //for nested unit of work |
| //make parent clone and register object |
| org.eclipse.persistence.internal.sessions.IdentityMapAccessor parentIdentityMapAccessor = unitOfWork.getParentIdentityMapSession(descriptor, false, false).getIdentityMapAccessorInstance(); |
| ((UnitOfWorkIdentityMapAccessor)parentIdentityMapAccessor).getAndCloneCacheKeyFromParent(primaryKey, null, theClass, shouldReturnInvalidatedObjects, descriptor); |
| //get the cachekey that was created in the parent. |
| cacheKey = parentIdentityMapAccessor.getCacheKeyForObject(primaryKey, theClass, descriptor, false); |
| } |
| |
| Object objectFromCache = null; |
| // this check could be simplified to one line but would create a window |
| // in which GC could remove the object and we would end up with a null pointer |
| // as well we must inspect the cacheKey without locking on it. |
| if ((cacheKey != null) && (shouldReturnInvalidatedObjects || !descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey))) { |
| synchronized (cacheKey) { |
| //if the object in the cachekey is null but the key is acquired then |
| //someone must be rebuilding it or creating a new one. Sleep until |
| // it's finished. A plain wait here would be more efficient but we may not |
| // get notified for quite some time (ie deadlock) if the other thread |
| //is building the object. Must wait and not sleep in order for the monitor to be released |
| objectFromCache = cacheKey.getObject(); |
| try { |
| while (cacheKey.isAcquired() && (objectFromCache == null)) { |
| cacheKey.wait(5); |
| } |
| } catch (InterruptedException ex) { |
| } |
| } |
| |
| // check for inheritance. |
| objectFromCache = checkForInheritance(objectFromCache, theClass, descriptor); |
| if (objectFromCache == null) { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| |
| // Consider read-only class CR#4094 |
| if (this.session.isClassReadOnly(theClass, descriptor)) { |
| // PERF: Just return the original object. |
| return objectFromCache; |
| } |
| |
| if (this.session instanceof RepeatableWriteUnitOfWork) { |
| Object unregisteredDeletedClone = ((RepeatableWriteUnitOfWork)this.session).getUnregisteredDeletedCloneForOriginal(objectFromCache); |
| if(unregisteredDeletedClone != null) { |
| return unregisteredDeletedClone; |
| } |
| } |
| |
| return unitOfWork.cloneAndRegisterObject(objectFromCache, cacheKey, descriptor); |
| } |
| |
| /** |
| * INTERNAL: |
| * 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. |
| * |
| * results are only cached in the parent session for UnitOfWorks |
| */ |
| @Override |
| public Object getQueryResult(ReadQuery query, List parameters, boolean checkExpiry) { |
| return this.session.getParent().getIdentityMapAccessorInstance().getQueryResult(query, parameters, checkExpiry); |
| } |
| |
| /** |
| * INTERNAL: |
| * 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. |
| * |
| * Results are only cached in the parent session for UnitOfWorks |
| */ |
| @Override |
| public void putQueryResult(ReadQuery query, List parameters, Object results) { |
| this.session.getParent().getIdentityMapAccessorInstance().putQueryResult(query, parameters, results); |
| } |
| |
| /** |
| * INTERNAL: |
| * Reset the entire object cache, |
| * ** be careful using this. |
| * This method blows away both this session's and its parents caches, |
| * this includes the server cache or any other cache. |
| * This throws away any objects that have been read in. |
| * Extreme caution should be used before doing this because object identity will no longer |
| * be maintained for any objects currently read in. This should only be called |
| * if the application knows that it no longer has references to object held in the cache. |
| */ |
| @Override |
| public void initializeAllIdentityMaps() { |
| super.initializeAllIdentityMaps(); |
| this.session.getParent().getIdentityMapAccessor().initializeAllIdentityMaps(); |
| } |
| |
| /** |
| * This method is used to resolve the inheritance issues arise while trying to get object from |
| * identity map of parent session. 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). |
| */ |
| protected Object checkForInheritance(Object domainObject, Class<?> superClass, ClassDescriptor descriptor) { |
| if ((domainObject != null) && ((domainObject.getClass() != superClass) && (!superClass.isInstance(domainObject)))) { |
| if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().getUseDescriptorsToValidateInheritedObjects()) { |
| if (descriptor.getInheritancePolicy().getSubclassDescriptor(domainObject.getClass()) == null) { |
| return null; |
| } |
| return domainObject; |
| } |
| return null; |
| } |
| return domainObject; |
| } |
| } |