| /* |
| * 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 |
| // 01/29/2019-3.0 Sureshkumar Balakrishnan |
| // - 541873: ENTITYMANAGER.DETACH() TRIGGERS LAZY LOADING INTO THE PERSISTENCE CONTEXT |
| package org.eclipse.persistence.internal.descriptors; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.indirection.IndirectCollection; |
| import org.eclipse.persistence.indirection.IndirectContainer; |
| import org.eclipse.persistence.indirection.ValueHolderInterface; |
| import org.eclipse.persistence.internal.queries.AttributeItem; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.queries.AttributeGroup; |
| import org.eclipse.persistence.queries.FetchGroup; |
| |
| /** |
| * This class provides a generic way of using the descriptor information |
| * to traverse an object graph. |
| * Define a subclass, or an inner class, that implements at least |
| * #iterate(Object) to implement a new traversal |
| * feature without having to change the mapping classes or the object builder. |
| * It provides functionality such as a cascading depth, a stack of visited object, |
| * and a collection of the visited objects. |
| * |
| * NOTE: |
| * If this works nicely the merge manager, remote traversals, and maybe |
| * even aspects of the commit manager could be converted to use this class. |
| */ |
| public abstract class DescriptorIterator { |
| public static final int NoCascading = 1; |
| public static final int CascadePrivateParts = 2; |
| public static final int CascadeAllParts = 3; |
| protected Map visitedObjects; |
| protected Stack visitedStack; |
| protected AbstractSession session; |
| protected DatabaseMapping currentMapping; |
| protected ClassDescriptor currentDescriptor; |
| protected AttributeItem currentItem; |
| protected AttributeGroup currentGroup; |
| protected boolean usesGroup; |
| /* Ignored if usesGroup is false. |
| * If set to true allows visiting the same object several times - |
| * as long as it hasn't been visited with the currentGroup. |
| */ |
| protected boolean shouldTrackCurrentGroup; |
| protected Object result;// this is a work area, typically used as a Collecting Parm |
| protected boolean shouldIterateOverIndirectionObjects; |
| protected boolean shouldIterateOverUninstantiatedIndirectionObjects; |
| protected boolean shouldIterateOverWrappedObjects; |
| protected boolean shouldIterateOnIndirectionObjects; |
| protected boolean shouldIterateOnAggregates; |
| protected boolean shouldIterateOnPrimitives; |
| // false by default; true means if object has FetchGroup then don't iterate outside it. |
| protected boolean shouldIterateOnFetchGroupAttributesOnly; |
| protected boolean shouldBreak; |
| protected int cascadeDepth;// see static constants below |
| protected CascadeCondition cascadeCondition; |
| protected boolean forDetach; |
| |
| /** |
| * Construct a typical iterator: |
| * iterate over all the objects |
| * process the objects contained by "value holders"... |
| * ...but only if they have already been instantiated... |
| * ...and don't process the "value holders" themselves |
| * process "wrapped" objects |
| * skip aggregate objects |
| * skip primitives (Strings, Dates, Integers, etc.) |
| */ |
| protected DescriptorIterator() { |
| // 2612538 - the default size of Map (32) is appropriate |
| this.visitedObjects = new IdentityHashMap(); |
| this.visitedStack = new Stack(); |
| this.cascadeDepth = CascadeAllParts; |
| this.shouldIterateOverIndirectionObjects = true;// process the objects contained by ValueHolders... |
| this.shouldIterateOverUninstantiatedIndirectionObjects = false;// ...but only if they have already been instantiated... |
| this.shouldIterateOnIndirectionObjects = false;// ...and don't process the ValueHolders themselves |
| this.shouldIterateOverWrappedObjects = true;// process "wrapped" objects |
| this.shouldIterateOnAggregates = false; |
| this.shouldIterateOnPrimitives = false; |
| this.shouldIterateOnFetchGroupAttributesOnly = false; |
| this.shouldBreak = false; |
| this.cascadeCondition = new CascadeCondition(); |
| } |
| |
| public int getCascadeDepth() { |
| return cascadeDepth; |
| } |
| |
| public ClassDescriptor getCurrentDescriptor() { |
| return currentDescriptor; |
| } |
| |
| public DatabaseMapping getCurrentMapping() { |
| return currentMapping; |
| } |
| |
| public AttributeItem getCurrentItem() { |
| return this.currentItem; |
| } |
| |
| public AttributeGroup getCurrentGroup() { |
| return this.currentGroup; |
| } |
| |
| /** |
| * Fetch and return the descriptor for the specified object. |
| */ |
| protected ClassDescriptor getDescriptorFor(Object object) { |
| ClassDescriptor result = getSession().getDescriptor(object); |
| if (result == null) { |
| throw DescriptorException.missingDescriptor(object.getClass().getName()); |
| } |
| return result; |
| } |
| |
| public Object getResult() { |
| return result; |
| } |
| |
| public AbstractSession getSession() { |
| return session; |
| } |
| |
| /** |
| * Return the second-to-last object visited. |
| */ |
| public Object getVisitedGrandparent() { |
| Object parent = getVisitedStack().pop(); |
| Object result = getVisitedStack().peek(); |
| getVisitedStack().push(parent); |
| return result; |
| } |
| |
| public Map getVisitedObjects() { |
| return visitedObjects; |
| } |
| |
| /** |
| * Return the last object visited. |
| */ |
| public Object getVisitedParent() { |
| return getVisitedStack().peek(); |
| } |
| |
| public Stack getVisitedStack() { |
| return visitedStack; |
| } |
| |
| /** |
| * Iterate an aggregate object |
| * (i.e. an object that is the target of an AggregateMapping). |
| * Override this method if appropriate. |
| */ |
| protected void internalIterateAggregateObject(Object aggregateObject) { |
| iterate(aggregateObject); |
| } |
| |
| /** |
| * Iterate an indirect container (IndirectList or IndirectMap). |
| * Override this method if appropriate. |
| */ |
| protected void internalIterateIndirectContainer(IndirectContainer container) { |
| iterate(container); |
| } |
| |
| /** |
| * Iterate a primitive object (String, Date, Integer, etc.). |
| * Override this method if appropriate. |
| */ |
| protected void internalIteratePrimitive(Object primitiveValue) { |
| iterate(primitiveValue); |
| } |
| |
| /** |
| * Iterate a (a non-Aggregate) reference object. |
| * Override this method if appropriate. |
| */ |
| protected void internalIterateReferenceObject(Object referenceObject) { |
| iterate(referenceObject); |
| } |
| |
| /** |
| * Iterate a value holder. |
| * Override this method if appropriate. |
| */ |
| protected void internalIterateValueHolder(ValueHolderInterface valueHolder) { |
| iterate(valueHolder); |
| } |
| |
| /** |
| * To define a new iterator create a subclass and define at least this method. |
| * Given an object or set of the objects, this method will be called on those |
| * objects and any object connected to them by using the descriptors to |
| * traverse the object graph. |
| * Override the assorted #internalIterate*() methods if appropriate. |
| */ |
| protected abstract void iterate(Object object); |
| |
| /** |
| * Iterate on the mapping's reference object and |
| * recursively iterate on the reference object's |
| * reference objects. |
| * This is used for aggregate and aggregate collection mappings, which are not iterated on by default. |
| */ |
| public void iterateForAggregateMapping(Object aggregateObject, DatabaseMapping mapping, ClassDescriptor descriptor) { |
| if (aggregateObject == null) { |
| return; |
| } |
| setCurrentMapping(mapping); |
| // aggregate descriptors are passed in because they could be part of an inheritance tree |
| setCurrentDescriptor(descriptor); |
| |
| AttributeGroup currentGroupOriginal = null; |
| AttributeItem currentItemOriginal = null; |
| if(this.usesGroup) { |
| currentGroupOriginal = this.currentGroup; |
| currentItemOriginal = this.currentItem; |
| this.currentGroup = this.currentItem.getGroup(); |
| } |
| |
| if (shouldIterateOnAggregates()) {// false by default |
| internalIterateAggregateObject(aggregateObject); |
| if (shouldBreak()) { |
| setShouldBreak(false); |
| if(this.usesGroup) { |
| this.currentGroup = currentGroupOriginal; |
| this.currentItem = currentItemOriginal; |
| } |
| return; |
| } |
| } |
| |
| iterateReferenceObjects(aggregateObject); |
| if(this.usesGroup) { |
| this.currentGroup = currentGroupOriginal; |
| this.currentItem = currentItemOriginal; |
| } |
| } |
| |
| /** |
| * Iterate on the indirection object for its mapping. |
| */ |
| public void iterateIndirectContainerForMapping(IndirectContainer container, DatabaseMapping mapping) { |
| setCurrentMapping(mapping); |
| setCurrentDescriptor(null); |
| |
| if (shouldIterateOnIndirectionObjects()) {// false by default |
| internalIterateIndirectContainer(container); |
| } |
| |
| if (shouldIterateOverUninstantiatedIndirectionObjects() || (shouldIterateOverIndirectionObjects() && container.isInstantiated()) || isForDetach()) { |
| // force instantiation only if specified |
| mapping.iterateOnRealAttributeValue(this, container); |
| } else if (shouldIterateOverIndirectionObjects()) { |
| // PERF: Allow the indirect container to iterate any cached elements. |
| if (container instanceof IndirectCollection) { |
| mapping.iterateOnRealAttributeValue(this, ((IndirectCollection)container).getAddedElements()); |
| } |
| } |
| } |
| |
| /** |
| * Iterate on the primitive value for its mapping. |
| */ |
| public void iteratePrimitiveForMapping(Object primitiveValue, DatabaseMapping mapping) { |
| if (primitiveValue == null) { |
| return; |
| } |
| setCurrentMapping(mapping); |
| setCurrentDescriptor(null); |
| |
| if (shouldIterateOnPrimitives()) {// false by default |
| AttributeGroup currentGroupOriginal = null; |
| AttributeItem currentItemOriginal = null; |
| if(this.usesGroup) { |
| currentGroupOriginal = this.currentGroup; |
| currentItemOriginal = this.currentItem; |
| this.currentGroup = this.currentItem.getGroup(); |
| } |
| |
| internalIteratePrimitive(primitiveValue); |
| |
| if(this.usesGroup) { |
| this.currentGroup = currentGroupOriginal; |
| this.currentItem = currentItemOriginal; |
| } |
| } |
| } |
| |
| /** |
| * Iterate on the mapping's reference object and |
| * recursively iterate on the reference object's |
| * reference objects. |
| */ |
| public void iterateReferenceObjectForMapping(Object referenceObject, DatabaseMapping mapping) { |
| if (this.cascadeCondition.shouldNotCascade(mapping)) { |
| return; |
| } |
| |
| // When using wrapper policy in EJB the iteration can stop in certain cases, |
| // this is because EJB forces beans to be registered anyway and clone identity can be violated |
| // and the violated clones references to session objects should not be traversed. |
| ClassDescriptor rd = mapping.getReferenceDescriptor(); |
| if ((!shouldIterateOverWrappedObjects()) && (rd != null) && (rd.hasWrapperPolicy())) { |
| return; |
| } |
| if (referenceObject == null) { |
| return; |
| } |
| |
| if(this.usesGroup && this.shouldTrackCurrentGroup) { |
| Set visited = (Set)getVisitedObjects().get(referenceObject); |
| if(visited == null) { |
| visited = new HashSet(1); |
| visited.add(this.currentItem.getGroup()); |
| getVisitedObjects().put(referenceObject, visited); |
| } else { |
| if(visited.contains(this.currentItem.getGroup())) { |
| // source object has been already visited with an equal group |
| return; |
| } else { |
| visited.add(this.currentItem.getGroup()); |
| } |
| } |
| } else { |
| // Check if already processed. |
| if (getVisitedObjects().containsKey(referenceObject)) { |
| return; |
| } |
| |
| getVisitedObjects().put(referenceObject, referenceObject); |
| } |
| setCurrentMapping(mapping); |
| setCurrentDescriptor(getDescriptorFor(referenceObject)); |
| |
| AttributeGroup currentGroupOriginal = null; |
| AttributeItem currentItemOriginal = null; |
| if(this.usesGroup) { |
| currentGroupOriginal = this.currentGroup; |
| currentItemOriginal = this.currentItem; |
| this.currentGroup = this.currentItem.getGroup(); |
| } |
| |
| internalIterateReferenceObject(referenceObject); |
| if (shouldBreak()) { |
| setShouldBreak(false); |
| if(this.usesGroup) { |
| this.currentGroup = currentGroupOriginal; |
| this.currentItem = currentItemOriginal; |
| } |
| return; |
| } |
| |
| iterateReferenceObjects(referenceObject); |
| if(this.usesGroup) { |
| this.currentGroup = currentGroupOriginal; |
| this.currentItem = currentItemOriginal; |
| } |
| } |
| |
| /** |
| * Iterate over the sourceObject's reference objects, |
| * updating the visited stack appropriately. |
| */ |
| protected void iterateReferenceObjects(Object sourceObject) { |
| if(this.usesGroup) { |
| // object is outside of the group - don't iterate over its references |
| if(this.currentGroup == null || !this.currentGroup.hasItems()) { |
| return; |
| } |
| } |
| |
| getVisitedStack().push(sourceObject); |
| internalIterateReferenceObjects(sourceObject); |
| getVisitedStack().pop(); |
| } |
| |
| protected void internalIterateReferenceObjects(Object sourceObject) { |
| List<DatabaseMapping> mappings; |
| // Only iterate on relationships if required. |
| if (shouldIterateOnPrimitives()) { |
| mappings = getCurrentDescriptor().getObjectBuilder().getDescriptor().getMappings(); |
| } else { |
| ObjectBuilder builder = getCurrentDescriptor().getObjectBuilder().getDescriptor().getObjectBuilder(); |
| // PERF: Only process relationships. |
| if (builder.isSimple()) { |
| return; |
| } |
| mappings = builder.getRelationshipMappings(); |
| } |
| |
| if (shouldIterateOnFetchGroupAttributesOnly()) { |
| if(getCurrentDescriptor().hasFetchGroupManager()) { |
| FetchGroup fetchGroup = getCurrentDescriptor().getFetchGroupManager().getObjectFetchGroup(sourceObject); |
| if (fetchGroup != null) { |
| List<DatabaseMapping> fetchGroupMappings = new ArrayList(); |
| for (DatabaseMapping mapping : mappings) { |
| if (fetchGroup.containsAttributeInternal(mapping.getAttributeName())) { |
| fetchGroupMappings.add(mapping); |
| } |
| } |
| mappings = fetchGroupMappings; |
| } |
| } |
| } |
| |
| if (this.usesGroup) { |
| AttributeGroup currentGroupOriginal = this.currentGroup; |
| AttributeItem currentItemOriginal = this.currentItem; |
| for (DatabaseMapping mapping : mappings) { |
| this.currentItem = this.currentGroup.getAllItems().get(mapping.getAttributeName()); |
| // iterate only over the mappings found in the group |
| if (currentItem != null) { |
| mapping.iterate(this); |
| this.currentGroup = currentGroupOriginal; |
| } |
| } |
| this.currentItem = currentItemOriginal; |
| } else { |
| for (DatabaseMapping mapping : mappings) { |
| mapping.iterate(this); |
| } |
| } |
| } |
| |
| /** |
| * Iterate on the value holder for its mapping. |
| */ |
| public void iterateValueHolderForMapping(ValueHolderInterface valueHolder, DatabaseMapping mapping) { |
| setCurrentMapping(mapping); |
| setCurrentDescriptor(null); |
| |
| if (shouldIterateOnIndirectionObjects()) {// false by default |
| internalIterateValueHolder(valueHolder); |
| } |
| |
| if (shouldIterateOverUninstantiatedIndirectionObjects() || (shouldIterateOverIndirectionObjects() && valueHolder.isInstantiated())) { |
| // force instantiation only if specified |
| mapping.iterateOnRealAttributeValue(this, valueHolder.getValue()); |
| } |
| } |
| |
| public void setCascadeDepth(int cascadeDepth) { |
| this.cascadeDepth = cascadeDepth; |
| } |
| |
| public void setCascadeCondition(CascadeCondition cascadeCondition){ |
| this.cascadeCondition = cascadeCondition; |
| } |
| |
| public void setCurrentDescriptor(ClassDescriptor currentDescriptor) { |
| this.currentDescriptor = currentDescriptor; |
| } |
| |
| public void setCurrentMapping(DatabaseMapping currentMapping) { |
| this.currentMapping = currentMapping; |
| } |
| |
| public void setCurrentItem(AttributeItem item) { |
| this.currentItem = item; |
| } |
| |
| public void setCurrentGroup(AttributeGroup group) { |
| this.currentGroup = group; |
| } |
| |
| public void setResult(Object result) { |
| this.result = result; |
| } |
| |
| public void setSession(AbstractSession session) { |
| this.session = session; |
| } |
| |
| public void setShouldBreak(boolean shouldBreak) { |
| this.shouldBreak = shouldBreak; |
| } |
| |
| /** |
| * Set whether the aggregate reference objects themselves |
| * should be processed. (The objects referenced by the aggregate |
| * objects will be processed either way.) |
| */ |
| public void setShouldIterateOnAggregates(boolean shouldIterateOnAggregates) { |
| this.shouldIterateOnAggregates = shouldIterateOnAggregates; |
| } |
| |
| /** |
| * Set whether the attributes outside fetch group should be processed. |
| */ |
| public void setShouldIterateOnFetchGroupAttributesOnly(boolean shouldIterateOnFetchGroupAttributesOnly) { |
| this.shouldIterateOnFetchGroupAttributesOnly = shouldIterateOnFetchGroupAttributesOnly; |
| } |
| |
| /** |
| * Set whether the indirection objects themselves (e.g. the ValueHolders) |
| * should be processed. |
| */ |
| public void setShouldIterateOnIndirectionObjects(boolean shouldIterateOnIndirectionObjects) { |
| this.shouldIterateOnIndirectionObjects = shouldIterateOnIndirectionObjects; |
| } |
| |
| /** |
| * Set whether to process primitive reference objects |
| * (e.g. Strings, Dates, ints). |
| */ |
| public void setShouldIterateOnPrimitives(boolean shouldIterateOnPrimitives) { |
| this.shouldIterateOnPrimitives = shouldIterateOnPrimitives; |
| } |
| |
| /** |
| * Set whether to process the objects contained by indirection objects |
| * (e.g. a ValueHolder's value) - but *without* instantiating them. |
| * @see #setShouldIterateOverUninstantiatedIndirectionObjects(boolean) |
| */ |
| public void setShouldIterateOverIndirectionObjects(boolean shouldIterateOverIndirectionObjects) { |
| this.shouldIterateOverIndirectionObjects = shouldIterateOverIndirectionObjects; |
| } |
| |
| /** |
| * Set whether to *instantiate* and process the objects |
| * contained by indirection objects (e.g. a ValueHolder's value). |
| */ |
| public void setShouldIterateOverUninstantiatedIndirectionObjects(boolean shouldIterateOverUninstantiatedIndirectionObjects) { |
| this.shouldIterateOverUninstantiatedIndirectionObjects = shouldIterateOverUninstantiatedIndirectionObjects; |
| } |
| |
| public void setShouldIterateOverWrappedObjects(boolean shouldIterateOverWrappedObjects) { |
| this.shouldIterateOverWrappedObjects = shouldIterateOverWrappedObjects; |
| } |
| |
| public void setShouldTrackCurrentGroup(boolean shouldTrackCurrentGroup) { |
| this.shouldTrackCurrentGroup = shouldTrackCurrentGroup; |
| } |
| |
| public void setVisitedObjects(Map visitedObjects) { |
| this.visitedObjects = visitedObjects; |
| } |
| |
| protected void setVisitedStack(Stack visitedStack) { |
| this.visitedStack = visitedStack; |
| } |
| |
| public boolean shouldBreak() { |
| return shouldBreak; |
| } |
| |
| public boolean shouldCascadeAllParts() { |
| return getCascadeDepth() == CascadeAllParts; |
| } |
| |
| public boolean shouldCascadeNoParts() { |
| return (getCascadeDepth() == NoCascading); |
| } |
| |
| public boolean shouldCascadePrivateParts() { |
| return (getCascadeDepth() == CascadeAllParts) || (getCascadeDepth() == CascadePrivateParts); |
| } |
| |
| /** |
| * Return whether the aggregate reference objects themselves |
| * should be processed. (The objects referenced by the aggregate |
| * objects will be processed either way.) |
| */ |
| public boolean shouldIterateOnAggregates() { |
| return shouldIterateOnAggregates; |
| } |
| |
| /** |
| * If true then if object has a FetchGroup then iterations |
| * not performed on mappings that are outside of the FetchGroup. |
| */ |
| public boolean shouldIterateOnFetchGroupAttributesOnly() { |
| return this.shouldIterateOnFetchGroupAttributesOnly; |
| } |
| |
| /** |
| * Return whether the indirection objects themselves (e.g. the ValueHolders) |
| * should be processed. |
| */ |
| public boolean shouldIterateOnIndirectionObjects() { |
| return shouldIterateOnIndirectionObjects; |
| } |
| |
| /** |
| * Return whether to process primitive reference objects |
| * (e.g. Strings, Dates, ints). |
| */ |
| public boolean shouldIterateOnPrimitives() { |
| return shouldIterateOnPrimitives; |
| } |
| |
| /** |
| * Return whether to process the objects contained by indirection objects |
| * (e.g. a ValueHolder's value) - but *without* instantiating them. |
| * @see #shouldIterateOverUninstantiatedIndirectionObjects() |
| */ |
| public boolean shouldIterateOverIndirectionObjects() { |
| return shouldIterateOverIndirectionObjects; |
| } |
| |
| /** |
| * Return whether to *instantiate* and process the objects |
| * contained by indirection objects (e.g. a ValueHolder's value). |
| */ |
| public boolean shouldIterateOverUninstantiatedIndirectionObjects() { |
| return shouldIterateOverUninstantiatedIndirectionObjects; |
| } |
| |
| public boolean shouldIterateOverWrappedObjects() { |
| return shouldIterateOverWrappedObjects; |
| } |
| |
| public boolean shouldTrackCurrentGroup() { |
| return this.shouldTrackCurrentGroup; |
| } |
| |
| public boolean usesGroup() { |
| return this.usesGroup; |
| } |
| |
| /** |
| * This is the root method called to start the iteration. |
| */ |
| public void startIterationOn(Object sourceObject) { |
| startIterationOn(sourceObject, null); |
| } |
| |
| public void startIterationOn(Object sourceObject, AttributeGroup group) { |
| this.usesGroup = group != null; |
| if(this.usesGroup && this.shouldTrackCurrentGroup) { |
| Set visited = (Set)getVisitedObjects().get(sourceObject); |
| if(visited == null) { |
| visited = new HashSet(1); |
| visited.add(group); |
| getVisitedObjects().put(sourceObject, visited); |
| } else { |
| if(visited.contains(group)) { |
| // source object has been already visited with an equal group |
| return; |
| } else { |
| visited.add(group); |
| } |
| } |
| } else { |
| if (getVisitedObjects().containsKey(sourceObject)) { |
| return; |
| } |
| getVisitedObjects().put(sourceObject, sourceObject); |
| } |
| setCurrentMapping(null); |
| setCurrentDescriptor(getSession().getDescriptor(sourceObject)); |
| setCurrentItem(null); |
| setCurrentGroup(group); |
| |
| iterate(sourceObject); |
| |
| // start the recursion |
| if ((getCurrentDescriptor() != null) && (!shouldCascadeNoParts()) && !this.shouldBreak()) { |
| iterateReferenceObjects(sourceObject); |
| } |
| } |
| |
| public class CascadeCondition{ |
| /** |
| * Default constructor. |
| */ |
| public CascadeCondition() { |
| } |
| |
| public boolean shouldNotCascade(DatabaseMapping mapping){ |
| return !(shouldCascadeAllParts() || (shouldCascadePrivateParts() && mapping.isPrivateOwned())); |
| } |
| } |
| |
| public boolean isForDetach() { |
| return forDetach; |
| } |
| |
| public void setForDetach(boolean forDetach) { |
| this.forDetach = forDetach; |
| } |
| } |