/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.internal.descriptors; import java.util.*; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.exceptions.*; import org.eclipse.persistence.indirection.*; import org.eclipse.persistence.internal.queries.AttributeItem; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.*; 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; /** * 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.) */ public 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())) { // 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() */ 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{ public boolean shouldNotCascade(DatabaseMapping mapping){ return !(shouldCascadeAllParts() || (shouldCascadePrivateParts() && mapping.isPrivateOwned())); } } }