blob: da02bfed819e3f16581f56bf0a2e4c12944cb653 [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
// 05/19/2010-2.1 ailitchev - Bug 244124 - Add Nested FetchGroup
package org.eclipse.persistence.descriptors;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.persistence.descriptors.changetracking.ObjectChangePolicy;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.queries.AttributeItem;
import org.eclipse.persistence.internal.queries.EntityFetchGroup;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.queries.AttributeGroup;
import org.eclipse.persistence.queries.FetchGroup;
import org.eclipse.persistence.queries.FetchGroupTracker;
/**
* <p><b>Purpose</b>: The fetch group manager controls the named fetch groups defined at
* the descriptor level. EclipseLink supports multiple, overlapped fetch groups, optionally with
* one of them as the default fetch group.
* <p>
* The domain object must implement org.eclipse.persistence.queries.FetchGroupTracker interface,
* in order to make use of the fetch group performance enhancement feature.
* <p>
* Please refer to FetchGroup class for the pros and cons of fetch group usage.
*
* @see org.eclipse.persistence.queries.FetchGroup
* @see org.eclipse.persistence.queries.FetchGroupTracker
*
* @author King Wang, dclarke
* @since TopLink 10.1.3.
*/
public class FetchGroupManager implements Cloneable, java.io.Serializable {
//The group map is keyed by the group name, valued by the fetch group object.
private Map<String, FetchGroup> fetchGroups = null;
// EntityFetchGroups mapped by their AttributeNames Sets.
private transient Map<Set<String>, EntityFetchGroup> entityFetchGroups = new ConcurrentHashMap();
//default fetch group
private FetchGroup defaultFetchGroup;
private EntityFetchGroup defaultEntityFetchGroup;
// full fetch group - contains all attributes, none of them nested.
private FetchGroup fullFetchGroup;
// minimal fetch group - contains primary key attribute(s) and version.
private FetchGroup minimalFetchGroup;
// identity fetch group - contains primary key attribute(s) only.
private EntityFetchGroup idEntityFetchGroup;
// non relational fetch group - contains intersection of non-relational attributes
// and defaultEntityFetchGroup.
private EntityFetchGroup nonReferenceEntityFetchGroup;
//ref to the descriptor
private ClassDescriptor descriptor;
// indicates whether defaultFetchGroup should be copied from the parent if not set.
private boolean shouldUseInheritedDefaultFetchGroup = true;
/**
* Constructor
*/
public FetchGroupManager() {
}
/**
* Add a named fetch group to the descriptor
*/
public void addFetchGroup(FetchGroup group) {
//create a new fetch group and put it in the group map.
getFetchGroups().put(group.getName(), group);
}
/**
* Return the fetch group map: keyed by the group name, valued by the fetch group object.
*/
public Map<String, FetchGroup> getFetchGroups() {
if (this.fetchGroups == null) {
//lazy initialized
this.fetchGroups = new HashMap<>(2);
}
return this.fetchGroups;
}
/**
* Return the descriptor-level default fetch group.
* All read object and read all queries would use the default fetch group if no fetch group
* is explicitly defined for the query, unless setShouldUseDefaultFetchGroup(false); is also
* called on the query.
*
* Default fetch group should be used carefully. It would be beneficial if most of the system queries
* are for the subset of the object, so un-needed attributes data would not have to be read, and the
* users do not have to setup every query for the given fetch group, as default one is always used.
* However, if queries on object are mostly use case specific and not systematic, using default fetch group
* could cause undesirable extra round-trip and performance degradation.
*
* @see org.eclipse.persistence.queries.ObjectLevelReadQuery#setShouldUseDefaultFetchGroup(boolean)
*/
public FetchGroup getDefaultFetchGroup() {
return this.defaultFetchGroup;
}
/**
* INTERNAL:
* Returns EntityFetchGroup corresponding to default FetchGroup.
*/
public EntityFetchGroup getDefaultEntityFetchGroup() {
return this.defaultEntityFetchGroup;
}
/**
* PUBLIC:
* Returns clone of the minimal fetch group.
* Could be used as a starting point for a new user-defined fetch group.
*/
public FetchGroup createMinimalFetchGroup() {
return this.minimalFetchGroup.clone();
}
/**
* PUBLIC:
* Indicates whether the passed fetch group is minimal.
*/
public boolean isMinimalFetchGroup(FetchGroup fetchGroup) {
return this.minimalFetchGroup.equals(fetchGroup);
}
/**
* INTERNAL:
* Returns EntityFetchGroup corresponding to primary key attribute(s).
*/
public EntityFetchGroup getIdEntityFetchGroup() {
return this.idEntityFetchGroup;
}
/**
* INTERNAL:
* Returns EntityFetchGroup corresponding to non relational attributes
* intersected with defaultFetchGroup.
*/
public EntityFetchGroup getNonReferenceEntityFetchGroup() {
return this.nonReferenceEntityFetchGroup;
}
/**
* INTERNAL:
* Returns EntityFetchGroup corresponding to non relational attributes
* intersected with defaultFetchGroup.
*/
public EntityFetchGroup getNonReferenceEntityFetchGroup(boolean addPk, boolean addVersion) {
if(addPk && addVersion) {
return getNonReferenceEntityFetchGroup();
}
FetchGroup nonReferenceFetchGroup = new FetchGroup();
for (DatabaseMapping mapping : getDescriptor().getMappings()) {
if(!mapping.isForeignReferenceMapping()) {
String name = mapping.getAttributeName();
if(this.defaultEntityFetchGroup == null || this.defaultEntityFetchGroup.containsAttribute(name)) {
nonReferenceFetchGroup.addAttribute(name);
}
}
}
if(addPk) {
for(DatabaseMapping mapping : descriptor.getObjectBuilder().getPrimaryKeyMappings()) {
String name = mapping.getAttributeName();
if(!nonReferenceFetchGroup.containsAttribute(name)) {
nonReferenceFetchGroup.addAttribute(name);
}
}
} else {
for(DatabaseMapping mapping : descriptor.getObjectBuilder().getPrimaryKeyMappings()) {
if(mapping.isForeignReferenceMapping()) {
String name = mapping.getAttributeName();
if(!nonReferenceFetchGroup.containsAttribute(name)) {
// always add foreign reference pk
nonReferenceFetchGroup.addAttribute(name);
}
}
}
}
if(addVersion) {
String lockAttribute = descriptor.getObjectBuilder().getLockAttribute();
if(lockAttribute != null) {
if(!nonReferenceFetchGroup.containsAttribute(lockAttribute)) {
nonReferenceFetchGroup.addAttribute(lockAttribute);
}
}
}
return getEntityFetchGroup(nonReferenceFetchGroup);
}
/**
* INTERNAL:
* Add primary key and version attributes to the passed fetch group.
*/
public void addMinimalFetchGroup(FetchGroup fetchGroup) {
if (this.minimalFetchGroup == null) {
return;
}
Iterator<String> it = this.minimalFetchGroup.getAttributeNames().iterator();
while(it.hasNext()) {
String name = it.next();
if(!fetchGroup.containsAttribute(name)) {
fetchGroup.addAttribute(name);
}
}
}
/**
* PUBLIC:
* Add primary key and version attributes to the passed fetch group
* and all the fetch group it contains.
* Also verifies that all the attributes have corresponding mappings.
* Could be used for fetch group preparation and validation.
* Called by ObjectLevelReadQuery prepareFetchgroup method.
*/
public void prepareAndVerify(FetchGroup fetchGroup) {
prepareAndVerifyInternal(fetchGroup, "");
}
/**
* INTERNAL:
* Add primary key and version attributes to the passed fetch group
* and all the fetch group it contains.
* Also verifies that all the attributes have corresponding mappings.
*/
protected void prepareAndVerifyInternal(FetchGroup fetchGroup, String attributePrefix) {
addMinimalFetchGroup(fetchGroup);
ObjectBuilder builder = this.descriptor.getObjectBuilder();
if (fetchGroup.isValidated()){
return;
}
Iterator<Map.Entry<String, AttributeItem>> it = fetchGroup.getAllItems().entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, AttributeItem> entry = it.next();
String name = entry.getKey();
DatabaseMapping mapping = builder.getMappingForAttributeName(name);
if(mapping != null) {
FetchGroup nestedFetchGroup = (FetchGroup)entry.getValue().getGroup();
if(nestedFetchGroup != null) {
if(mapping.isForeignReferenceMapping()) {
ClassDescriptor referenceDescriptor = mapping.getReferenceDescriptor();
if(referenceDescriptor != null) {
FetchGroupManager nestedFetchGroupManager = referenceDescriptor.getFetchGroupManager();
if(nestedFetchGroupManager != null) {
nestedFetchGroupManager.prepareAndVerifyInternal(nestedFetchGroup, attributePrefix + name + '.');
} else {
// target descriptor does not support fetch groups
throw ValidationException.fetchGroupHasWrongReferenceClass(fetchGroup, name);
}
} else {
// no reference descriptor found
throw ValidationException.fetchGroupHasWrongReferenceAttribute(fetchGroup, name);
}
} else if (mapping.isAggregateObjectMapping()){
ClassDescriptor referenceDescriptor = mapping.getReferenceDescriptor();
if(referenceDescriptor != null) {
FetchGroupManager nestedFetchGroupManager = referenceDescriptor.getFetchGroupManager();
if(nestedFetchGroupManager != null) {
nestedFetchGroupManager.prepareAndVerifyInternal(nestedFetchGroup, attributePrefix + name + '.');
} else {
// target descriptor does not support fetch groups
throw ValidationException.fetchGroupHasWrongReferenceClass(fetchGroup, name);
}
} else {
// no reference descriptor found
throw ValidationException.fetchGroupHasWrongReferenceAttribute(fetchGroup, name);
}
} else {
// no reference mapping found
throw ValidationException.fetchGroupHasWrongReferenceAttribute(fetchGroup, name);
}
}
} else {
// no mapping found
throw ValidationException.fetchGroupHasUnmappedAttribute(fetchGroup, name);
}
}
}
/**
* PUBLIC:
* Returns clone of the default fetch group.
* Could be used as a starting point for a new user-defined fetch group.
*/
public FetchGroup createDefaultFetchGroup() {
return this.defaultFetchGroup.clone();
}
/**
* PUBLIC:
* Returns clone of the full fetch group - contains all the attributes, no nesting.
* Could be used as a starting point for a new user-defined fetch group.
*/
public FetchGroup createFullFetchGroup() {
return this.fullFetchGroup.clone();
}
/**
* PUBLIC:
* Indicates whether the passed fetch group contains all the attributes, no nesting.
*/
public boolean isFullFetchGroup(FetchGroup fetchGroup) {
return this.fullFetchGroup.equals(fetchGroup);
}
/**
* INTERNAL:
* Returns entity fetch group corresponding to the passed set of attributes.
*/
public EntityFetchGroup getEntityFetchGroup(Set<String> attributeNames) {
if(attributeNames == null || attributeNames.isEmpty()) {
return null;
}
EntityFetchGroup entityFetchGroup = this.entityFetchGroups.get(attributeNames);
if(entityFetchGroup == null) {
entityFetchGroup = new EntityFetchGroup(attributeNames);
// EntityFetchGroup that contains all attributes is equivalent to no fetch group
if(entityFetchGroup.equals(this.fullFetchGroup)) {
return null;
}
this.entityFetchGroups.put(entityFetchGroup.getAttributeNames(), entityFetchGroup);
}
return entityFetchGroup;
}
/**
* INTERNAL:
* Returns entity fetch group corresponding to the passed fetch group.
*/
public EntityFetchGroup getEntityFetchGroup(FetchGroup fetchGroup) {
if(fetchGroup == null) {
return null;
}else{
return fetchGroup.getEntityFetchGroup(this);
}
}
/**
* Return a pre-defined named fetch group.
*
* Lookup the FetchGroup to use given a name taking into
* consideration descriptor inheritance to ensure parent descriptors are
* searched for named FetchGroups.
*/
public FetchGroup getFetchGroup(String groupName) {
FetchGroup fg = this.fetchGroups.get(groupName);
if (fg == null){
AttributeGroup ag = this.descriptor.getAttributeGroup(groupName);
if (ag != null){
fg = ag.toFetchGroup();
}
}
if (fg == null && getDescriptor().isChildDescriptor()) {
ClassDescriptor current = this.descriptor;
while (fg == null && current.isChildDescriptor()) {
ClassDescriptor parent = current.getInheritancePolicy().getParentDescriptor();
if (parent.hasFetchGroupManager()) {
fg = parent.getFetchGroupManager().getFetchGroup(groupName);
}
if (fg == null){
AttributeGroup ag = parent.getAttributeGroup(groupName);
if (ag != null){
fg = ag.toFetchGroup();
}
}
current = parent;
}
}
return fg;
}
/**
* Lookup the FetchGroup to use given a name and a flag taking into
* consideration descriptor inheritance to ensure parent descriptors are
* searched for named and default FetchGroup. This is used to determine the
* FetchGroup to use in a query's prepare.
*/
public FetchGroup getFetchGroup(String groupName, boolean useDefault) {
FetchGroup fg = null;
if (groupName != null) {
fg = getFetchGroup(groupName);
}
// Process default using hierarchy
if (fg == null && useDefault) {
fg = getDefaultFetchGroup();
}
return fg;
}
/**
* Set the descriptor-level default fetch group.
* All read object and read all queries would use the default fetch group if no fetch group is
* explicitly defined for the query, unless setShouldUseDefaultFetchGroup(false);
* is also called on the query.
*
* Default fetch group should be used carefully. It would be beneficial if most of the system queries
* are for the subset of the object, so un-needed attributes data would not have to be read, and the
* users do not have to setup every query for the given fetch group, as default one is always used.
* However, if queries on object are mostly use case specific and not systematic, using default fetch group
* could cause undesirable extra round-trip and performance degradation.
*
* @see org.eclipse.persistence.queries.ObjectLevelReadQuery#setShouldUseDefaultFetchGroup(boolean)
*/
public void setDefaultFetchGroup(FetchGroup newDefaultFetchGroup) {
if(this.defaultFetchGroup != newDefaultFetchGroup) {
if(this.descriptor.isFullyInitialized()) {
// don't do that before descriptors are initialized.
if(newDefaultFetchGroup != null) {
prepareAndVerify(newDefaultFetchGroup);
this.defaultEntityFetchGroup = this.getEntityFetchGroup(newDefaultFetchGroup);
} else {
this.defaultEntityFetchGroup = null;
}
}
this.defaultFetchGroup = newDefaultFetchGroup;
if(this.descriptor.isFullyInitialized()) {
// don't do that before descriptors are initialized.
initNonReferenceEntityFetchGroup();
}
}
}
/**
* INTERNAL:
* Return true if the object is partially fetched and cached.
* It applies to the query with fetch group.
*/
public boolean isPartialObject(Object domainObject) {
if (domainObject != null) {
FetchGroup fetchGroupInCache = ((FetchGroupTracker)domainObject)._persistence_getFetchGroup();
//if the fetch group reference is not null, it means the object is partial.
return (fetchGroupInCache != null);
}
return false;
}
/**
* INTERNAL:
* Return if the cached object data is sufficiently valid against a fetch group
*/
public boolean isObjectValidForFetchGroup(Object object, FetchGroup fetchGroup) {
FetchGroup groupInObject = ((FetchGroupTracker)object)._persistence_getFetchGroup();
return (groupInObject == null) || groupInObject.isSupersetOf(fetchGroup);
}
/**
* INTERNAL:
* Return true if the cached object data should be written in clone.
* It is used in Fetch Group case when filling in the clone from the cached object.
*/
public boolean shouldWriteInto(Object cachedObject, Object clone) {
FetchGroup fetchGroupInTarg = ((FetchGroupTracker)clone)._persistence_getFetchGroup();
if (fetchGroupInTarg != null) {
FetchGroup fetchGroupInSrc = ((FetchGroupTracker)cachedObject)._persistence_getFetchGroup();
//should write if target's fetch group is not a superset of that of the source,
//or if refresh is required, should always write (either refresh or revert) data from the cache to the clones.
return !fetchGroupInTarg.isSupersetOf(fetchGroupInSrc) || ((FetchGroupTracker) cachedObject)._persistence_shouldRefreshFetchGroup();
}
return false;
}
/**
* INTERNAL:
* Write data of the partially fetched object into the working and backup clones
*/
public void writePartialIntoClones(Object partialObject, Object workingClone, Object backupClone, UnitOfWorkImpl uow) {
FetchGroup fetchGroupInClone = ((FetchGroupTracker)workingClone)._persistence_getFetchGroup();
FetchGroup fetchGroupInObject = ((FetchGroupTracker)partialObject)._persistence_getFetchGroup();
// Update fetch group in clone as the union of two,
// do this first to avoid fetching during method access.
EntityFetchGroup union = flatUnionFetchGroups(fetchGroupInObject, fetchGroupInClone, false);// this method is not called for aggregates
// Finally, update clone's fetch group reference.
setObjectFetchGroup(workingClone, union, uow);
if (workingClone != backupClone) {
setObjectFetchGroup(backupClone, union, uow);
}
ObjectChangePolicy policy = descriptor.getObjectChangePolicy();
// Turn it 'off' to prevent unwanted events.
policy.dissableEventProcessing(workingClone);
try {
//if refresh is set, force to fill in fetch group data
if (((FetchGroupTracker)partialObject)._persistence_shouldRefreshFetchGroup()) {
//refresh and fill in the fetch group data
refreshFetchGroupIntoClones(partialObject, workingClone, backupClone, fetchGroupInObject, fetchGroupInClone, uow);
} else {//no refresh is enforced
//revert the unfetched attributes of the clones.
revertDataIntoUnfetchedAttributesOfClones(partialObject, workingClone, backupClone, fetchGroupInObject, fetchGroupInClone, uow);
}
} finally {
policy.enableEventProcessing(workingClone);
}
}
/**
* INTERNAL:
* Refresh the fetch group data into the working and backup clones.
* This is called if refresh is enforced
*/
// TODO-244124-dclarke: Needs to be updated to reflect new FetchGroup behaviour
private void refreshFetchGroupIntoClones(Object cachedObject, Object workingClone, Object backupClone, FetchGroup fetchGroupInObject, FetchGroup fetchGroupInClone, UnitOfWorkImpl uow) {
Vector<DatabaseMapping> mappings = descriptor.getMappings();
boolean isObjectPartial = (fetchGroupInObject != null);
Set fetchedAttributes = isObjectPartial ? fetchGroupInObject.getAttributeNames() : null;
int size = mappings.size();
for (int index = 0; index < size; index++) {
DatabaseMapping mapping = mappings.get(index);
if ((!isObjectPartial) || ((fetchedAttributes != null) && fetchedAttributes.contains(mapping.getAttributeName()))) {
// Only refresh the fetched attributes into clones.
mapping.buildClone(cachedObject, null, workingClone, null, uow);
if (workingClone != backupClone) {
mapping.buildClone(workingClone, null, backupClone, null, uow);
}
} else if (mapping.isAggregateObjectMapping()){
Object attributeValue = mapping.getAttributeValueFromObject(cachedObject);
Object cloneAttrbute = mapping.getAttributeValueFromObject(workingClone);
Object backupAttribute = mapping.getAttributeValueFromObject(backupClone);
if ((cloneAttrbute == null && attributeValue != null) || (cloneAttrbute != null && attributeValue == null)){
mapping.buildClone(cachedObject, null, workingClone, null, uow);
}else if (attributeValue != null && mapping.getReferenceDescriptor().getFetchGroupManager().shouldWriteInto(attributeValue, cloneAttrbute)) {
//there might be cases when reverting/refreshing clone is needed.
mapping.getReferenceDescriptor().getFetchGroupManager().writePartialIntoClones(attributeValue, cloneAttrbute, backupAttribute, uow);
}
}
}
}
/**
* Revert the clones' unfetched attributes, and leave fetched ones intact.
*/
private void revertDataIntoUnfetchedAttributesOfClones(Object cachedObject, Object workingClone, Object backupClone, FetchGroup fetchGroupInObject, FetchGroup fetchGroupInClone, UnitOfWorkImpl uow) {
// Fetched attributes set in working clone.
Set<String> fetchedAttributesClone = fetchGroupInClone.getAttributeNames();
// Fetched attributes set in cached object.
Set fetchedAttributesCached = null;
if (fetchGroupInObject != null) {
fetchedAttributesCached = fetchGroupInObject.getAttributeNames();
}
for (DatabaseMapping mapping : descriptor.getMappings()) {
String attributeName = mapping.getAttributeName();
if ((fetchedAttributesCached == null || fetchedAttributesCached.contains(attributeName)) && !fetchedAttributesClone.contains(attributeName)) {
mapping.buildClone(cachedObject, null, workingClone, null, uow);
if (workingClone != backupClone) {
mapping.buildClone(workingClone, null, backupClone, null, uow);
}
}else if (mapping.isAggregateObjectMapping()){
if (mapping.getReferenceDescriptor().hasFetchGroupManager()){
Object attributeValue = mapping.getAttributeValueFromObject(cachedObject);
Object cloneAttrbute = mapping.getAttributeValueFromObject(workingClone);
Object backupAttribute = mapping.getAttributeValueFromObject(backupClone);
if ((cloneAttrbute == null && attributeValue != null) || (cloneAttrbute != null && attributeValue == null)){
mapping.buildClone(cachedObject, null, workingClone, null, uow);
}else if (attributeValue != null && mapping.getReferenceDescriptor().getFetchGroupManager().shouldWriteInto(attributeValue, cloneAttrbute)) {
//there might be cases when reverting/refreshing clone is needed.
mapping.getReferenceDescriptor().getFetchGroupManager().writePartialIntoClones(attributeValue, cloneAttrbute, backupAttribute, uow);
}
}
}
// Only revert the attribute which is fetched by the cached object, but not fetched by the clone.
}
}
/**
* INTERNAL:
* Copy fetch group reference from the source object to the target
*/
public void copyAggregateFetchGroupInto(Object source, Object target, Object rootEntity, AbstractSession session) {
if (isPartialObject(source)) {
FetchGroup newGroup = ((FetchGroupTracker)source)._persistence_getFetchGroup().clone(); // must clone because original is linked to orig root
newGroup.setRootEntity((FetchGroupTracker) rootEntity);
setObjectFetchGroup(target, newGroup, session);
}
}
/**
* INTERNAL:
* Copy fetch group reference from the source object to the target
*/
public void copyFetchGroupInto(Object source, Object target, AbstractSession session) {
if (isPartialObject(source)) {
setObjectFetchGroup(target, ((FetchGroupTracker)source)._persistence_getFetchGroup(), session);
}
}
/**
* INTERNAL:
* Union the fetch group of the domain object with the new fetch group.
*/
public void unionEntityFetchGroupIntoObject(Object source, EntityFetchGroup newEntityFetchGroup, AbstractSession session, boolean shouldClone) {
//this order is important as we need to be merging into the target fetchgroup
setObjectFetchGroup(source, flatUnionFetchGroups(newEntityFetchGroup, ((FetchGroupTracker)source)._persistence_getFetchGroup(), shouldClone), session);
}
/**
* INTERNAL:
* Union two fetch groups.
*/
public FetchGroup unionFetchGroups(FetchGroup first, FetchGroup second) {
if ((first == null) || (second == null)) {
return null;
}
//return the superset if applied
if (first == second || first.isSupersetOf(second)) {
return first;
} else if (second.isSupersetOf(first)) {
return second;
}
Set<String> unionAttributeNames = new HashSet();
unionAttributeNames.addAll(first.getAttributeNames());
unionAttributeNames.addAll(second.getAttributeNames());
return getEntityFetchGroup(unionAttributeNames);
}
/**
* INTERNAL:
* Union two fetch groups as EntityFetchGroups.
* Ignores all nested attributes.
*/
public EntityFetchGroup flatUnionFetchGroups(FetchGroup first, FetchGroup second, boolean shouldClone) {
if ((first == null) || (second == null)) {
return null;
}
//return the superset if applied
if (first == second) {
return getEntityFetchGroup(first);
}
Set<String> unionAttributeNames = new HashSet();
unionAttributeNames.addAll(first.getAttributeNames());
unionAttributeNames.addAll(second.getAttributeNames());
EntityFetchGroup newGroup = getEntityFetchGroup(unionAttributeNames);
if (shouldClone){
newGroup = (EntityFetchGroup) newGroup.clone();
newGroup.setRootEntity(second.getRootEntity());
}
return newGroup;
}
/**
* INTERNAL:
* Reset object attributes to the default values.
*/
public void reset(Object source) {
((FetchGroupTracker)source)._persistence_resetFetchGroup();
}
/**
* INTERNAL:
* Return FetchGroup held by the object.
*/
public FetchGroup getObjectFetchGroup(Object domainObject) {
if (domainObject != null) {
return ((FetchGroupTracker)domainObject)._persistence_getFetchGroup();
}
return null;
}
/**
* INTERNAL:
* Return FetchGroup held by the object.
*/
public EntityFetchGroup getObjectEntityFetchGroup(Object domainObject) {
if (domainObject != null) {
FetchGroup fetchGroup = ((FetchGroupTracker)domainObject)._persistence_getFetchGroup();
if(fetchGroup != null) {
if(fetchGroup.isEntityFetchGroup()) {
return (EntityFetchGroup)fetchGroup;
}
return getEntityFetchGroup(fetchGroup);
}
}
return null;
}
/**
* INTERNAL:
* Set fetch group into the object.
*/
public void setObjectFetchGroup(Object source, FetchGroup fetchGroup, AbstractSession session) {
FetchGroupTracker tracker = (FetchGroupTracker)source;
if(fetchGroup == null) {
tracker._persistence_setFetchGroup(null);
tracker._persistence_setSession(null);
} else {
if(fetchGroup.isEntityFetchGroup()) {
// it's EntityFetchGroup - just set it
tracker._persistence_setFetchGroup(fetchGroup);
tracker._persistence_setSession(session);
} else {
EntityFetchGroup entityFetchGroup = this.getEntityFetchGroup(fetchGroup);
if(entityFetchGroup != null) {
tracker._persistence_setFetchGroup(entityFetchGroup);
tracker._persistence_setSession(session);
} else {
tracker._persistence_setFetchGroup(null);
tracker._persistence_setSession(null);
}
}
}
}
/**
* INTERNAL:
* Set if the tracked object is fetched from executing a query with or without refresh.
*/
public void setRefreshOnFetchGroupToObject(Object source, boolean shouldRefreshOnFetchgroup) {
((FetchGroupTracker)source)._persistence_setShouldRefreshFetchGroup(shouldRefreshOnFetchgroup);
}
/**
* Return true if the attribute of the object has already been fetched
*/
public boolean isAttributeFetched(Object entity, String attributeName) {
FetchGroup fetchGroup = ((FetchGroupTracker) entity)._persistence_getFetchGroup();
if (fetchGroup == null) {
return true;
}
return fetchGroup.containsAttributeInternal(attributeName);
}
/**
* PUBLIC:
* Return the referenced descriptor.
*/
public ClassDescriptor getDescriptor() {
return descriptor;
}
/**
* Set the referenced descriptor.
*/
public void setDescriptor(ClassDescriptor descriptor) {
this.descriptor = descriptor;
}
/**
* Return true if a fetch group exists for the given group name.
*/
public boolean hasFetchGroup(String groupName) {
return getFetchGroups().containsKey(groupName);
}
/**
* INTERNAL: Initialize the fetch groups. XXX-dclarke: added support for
* reinit the query manager's queries if they exist
*/
public void initialize(AbstractSession session) throws DescriptorException {
if (this.entityFetchGroups == null) {
this.entityFetchGroups = new ConcurrentHashMap();
}
if (!(Helper.classImplementsInterface(getDescriptor().getJavaClass(), ClassConstants.FetchGroupTracker_class))) {
//to use fetch group, the domain class must implement FetchGroupTracker interface
session.getIntegrityChecker().handleError(DescriptorException.needToImplementFetchGroupTracker(getDescriptor().getJavaClass(), getDescriptor()));
}
this.minimalFetchGroup = new FetchGroup();
this.fullFetchGroup = new FetchGroup();
for (DatabaseMapping mapping : getDescriptor().getMappings()) {
String name = mapping.getAttributeName();
if(mapping.isPrimaryKeyMapping()) {
this.minimalFetchGroup.addAttribute(name);
}
this.fullFetchGroup.addAttribute(name);
}
this.idEntityFetchGroup = null;
if (this.descriptor.isChildDescriptor()) {
FetchGroupManager parentManager = this.descriptor.getInheritancePolicy().getParentDescriptor().getFetchGroupManager();
if (parentManager != null) {
// copy idEntityFetchGroup from the parent
this.idEntityFetchGroup = parentManager.getIdEntityFetchGroup();
}
}
if (this.idEntityFetchGroup != null) {
// insert the copied idEntityFetchGroup into the map
this.entityFetchGroups.put(this.idEntityFetchGroup.getAttributeNames(), this.idEntityFetchGroup);
} else {
// currently minimalFetchGroup contains only PrimaryKey - that's what idEntityFetchGroup will consist of.
this.idEntityFetchGroup = getEntityFetchGroup(this.minimalFetchGroup);
}
if(this.descriptor.usesOptimisticLocking()) {
DatabaseField lockField = this.descriptor.getOptimisticLockingPolicy().getWriteLockField();
if (lockField != null) {
DatabaseMapping lockMapping = getDescriptor().getObjectBuilder().getMappingForField(lockField);
if (lockMapping != null) {
String attributeName = lockMapping.getAttributeName();
minimalFetchGroup.addAttribute(attributeName);
}
}
}
// Now minimalFetchGroup contains PrimaryKey plus locking field - getEntityFetchGroup call ensures
// that corresponding EntityFetchGroup is cached in entityFetchGroups map.
// Note that the new EntityFetchGroup is not created if there is no locking field.
getEntityFetchGroup(this.minimalFetchGroup);
}
/**
* INTERNAL:
* postInitialize called for inheritance children first.
* That allows to copy defaultFetchGroup from the parent only in case
* it has been set by user (not automatically generated).
*/
public void postInitialize(AbstractSession session) throws DescriptorException {
if (!(Helper.classImplementsInterface(getDescriptor().getJavaClass(), ClassConstants.FetchGroupTracker_class))) {
// initialize already threw exception here
return;
}
// Create and cache EntityFetchGroups for named fetch groups.
if(this.fetchGroups != null) {
Iterator<FetchGroup> it = this.fetchGroups.values().iterator();
while(it.hasNext()) {
FetchGroup fetchGroup = it.next();
prepareAndVerify(fetchGroup);
getEntityFetchGroup(fetchGroup);
}
}
if(this.defaultFetchGroup == null) {
// Look up default fetch group set by user on parent descriptors
if(this.descriptor.isChildDescriptor() && this.shouldUseInheritedDefaultFetchGroup) {
ClassDescriptor current = this.descriptor;
while(current.isChildDescriptor()) {
ClassDescriptor parent = current.getInheritancePolicy().getParentDescriptor();
if (parent.hasFetchGroupManager()) {
this.defaultFetchGroup = parent.getFetchGroupManager().getDefaultFetchGroup();
if(this.defaultFetchGroup != null) {
return;
}
}
current = parent;
}
}
FetchGroup defaultCandidate = new FetchGroup();
boolean hasLazy = false;
for (DatabaseMapping mapping : getDescriptor().getMappings()) {
if (mapping.isForeignReferenceMapping() || (!mapping.isLazy())) {
defaultCandidate.addAttribute(mapping.getAttributeName());
} else {
hasLazy = true;
}
}
if(hasLazy) {
this.defaultFetchGroup = defaultCandidate;
}
}
if(this.defaultFetchGroup != null) {
prepareAndVerify(this.defaultFetchGroup);
this.defaultEntityFetchGroup = getEntityFetchGroup(this.defaultFetchGroup);
}
initNonReferenceEntityFetchGroup();
}
protected void initNonReferenceEntityFetchGroup() {
FetchGroup nonReferenceFetchGroup = new FetchGroup();
for (DatabaseMapping mapping : getDescriptor().getMappings()) {
if(!mapping.isForeignReferenceMapping()) {
String name = mapping.getAttributeName();
if(this.defaultEntityFetchGroup == null || this.defaultEntityFetchGroup.containsAttribute(name)) {
nonReferenceFetchGroup.addAttribute(name);
}
}
}
this.addMinimalFetchGroup(nonReferenceFetchGroup);
this.nonReferenceEntityFetchGroup = getEntityFetchGroup(nonReferenceFetchGroup);
}
/**
* INTERNAL:
* Clone the fetch group manager.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (Exception exception) {
throw new InternalError(exception.toString());
}
}
/**
* PUBLIC:
* Set whether defaultFetchGroup should be copied from the parent if not set.
*/
public void setShouldUseInheritedDefaultFetchGroup(boolean shouldUseInheritedDefaultFetchGroup) {
this.shouldUseInheritedDefaultFetchGroup = shouldUseInheritedDefaultFetchGroup;
}
/**
* PUBLIC:
* Indicates whether defaultFetchGroup should be copied from the parent if not set.
*/
public boolean shouldUseInheritedDefaultFetchGroup() {
return this.shouldUseInheritedDefaultFetchGroup;
}
}