| /* |
| * 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; |
| } |
| } |