blob: 75ef3047f5ec80bfb8c3dc33c4f71a1b7beaba86 [file] [log] [blame]
/*
* 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;
}
}