blob: adce72b07314cc7008121650b7adce651a128a8d [file] [log] [blame]
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.access;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.query.PrefetchProcessor;
import org.apache.cayenne.query.PrefetchTreeNode;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.reflect.ArcProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
final class PrefetchProcessorTreeBuilder implements PrefetchProcessor {
private QueryMetadata queryMetadata;
private DataContext context;
private PrefetchProcessorNode root;
private LinkedList<PrefetchProcessorNode> nodeStack;
private ClassDescriptor descriptor;
private List<DataRow> mainResultRows;
private Map<String, List<?>> extraResultsByPath;
private Map<ObjectId, Persistent> seen;
PrefetchProcessorTreeBuilder(HierarchicalObjectResolver objectTreeResolver, List<DataRow> mainResultRows,
Map<String, List<?>> extraResultsByPath) {
this.context = objectTreeResolver.context;
this.queryMetadata = objectTreeResolver.queryMetadata;
this.mainResultRows = mainResultRows;
this.extraResultsByPath = extraResultsByPath;
this.descriptor = objectTreeResolver.descriptor;
}
PrefetchProcessorNode buildTree(PrefetchTreeNode tree) {
// reset state
this.nodeStack = new LinkedList<>();
// 'seen' is used to avoid re-processing objects already processed in a
// given prefetch query (see CAY-1695 for why this is bad). It is
// essentially a map of all objects fetched in a given transaction
// TODO: there are other places that are attempting to track objects in
// a tx... can we reuse 'seen' map from here?
this.seen = new HashMap<>();
this.root = null;
tree.traverse(this);
if (root == null) {
throw new CayenneRuntimeException("Failed to create prefetch processing tree.");
}
return root;
}
public boolean startPhantomPrefetch(PrefetchTreeNode node) {
// root should be treated as disjoint
if (getParent() == null) {
return startDisjointPrefetch(node);
} else {
PrefetchProcessorNode decorated = new PrefetchProcessorNode(getParent(), node.getName());
decorated.setPhantom(true);
return addNode(decorated);
}
}
private PrefetchProcessorNode createDisjointNode(PrefetchTreeNode node) {
// TODO, Andrus, 11/16/2005 - minor inefficiency: 'adjacentJointNodes'
// would
// grab ALL nodes, we just need to find first and stop...
PrefetchProcessorNode decorated = !node.adjacentJointNodes().isEmpty() ? new PrefetchProcessorJointNode(
getParent(), node.getName()) : new PrefetchProcessorNode(getParent(), node.getName());
decorated.setPhantom(false);
return decorated;
}
public boolean startDisjointPrefetch(PrefetchTreeNode node) {
// look ahead for joint children as joint children will require a
// different
// node type.
PrefetchProcessorNode decorated = createDisjointNode(node);
// semantics has to be "DISJOINT" even if the node is joint, as
// semantics
// defines relationship with parent..
decorated.setSemantics(PrefetchTreeNode.DISJOINT_PREFETCH_SEMANTICS);
return addNode(decorated);
}
public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) {
// see startDisjointPrefetch for comments
PrefetchProcessorNode decorated = createDisjointNode(node);
decorated.setSemantics(PrefetchTreeNode.DISJOINT_BY_ID_PREFETCH_SEMANTICS);
return addNode(decorated);
}
public boolean startJointPrefetch(PrefetchTreeNode node) {
PrefetchProcessorJointNode decorated = new PrefetchProcessorJointNode(getParent(), node.getName());
decorated.setPhantom(false);
decorated.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
boolean result = addNode(decorated);
// set "jointChildren" flag on all nodes in the same "join group"
PrefetchProcessorNode groupNode = decorated;
while (groupNode.getParent() != null && !groupNode.isDisjointPrefetch() && !groupNode.isDisjointByIdPrefetch()) {
groupNode = (PrefetchProcessorNode) groupNode.getParent();
groupNode.setJointChildren(true);
}
return result;
}
public boolean startUnknownPrefetch(PrefetchTreeNode node) {
// handle unknown as disjoint...
return startDisjointPrefetch(node);
}
public void finishPrefetch(PrefetchTreeNode node) {
// pop stack...
nodeStack.removeLast();
}
boolean addNode(PrefetchProcessorNode node) {
List<DataRow> rows;
ArcProperty arc;
ClassDescriptor descriptor;
PrefetchProcessorNode currentNode = getParent();
if (currentNode != null) {
@SuppressWarnings("unchecked")
List<DataRow> dataRows = (List<DataRow>)extraResultsByPath.get(node.getPath());
rows = dataRows;
arc = (ArcProperty) currentNode.getResolver().getDescriptor().getProperty(node.getName());
if (arc == null) {
throw new CayenneRuntimeException("No relationship with name '%s' found in entity '%s'"
, node.getName(), currentNode.getResolver().getEntity().getName());
}
descriptor = arc.getTargetDescriptor();
} else {
arc = null;
if (this.descriptor != null) {
descriptor = this.descriptor;
} else {
descriptor = queryMetadata.getClassDescriptor();
}
rows = mainResultRows;
}
node.setDataRows(rows);
node.setIncoming(arc);
if (node.getParent() != null && !node.isJointPrefetch()) {
node.setResolver(new HierarchicalObjectResolverNode(node, context, descriptor, queryMetadata
.isRefreshingObjects(), seen));
} else {
node.setResolver(new PrefetchObjectResolver(context, descriptor, queryMetadata.isRefreshingObjects(), seen));
}
if (node.getParent() == null || node.getParent().isPhantom()) {
node.setParentAttachmentStrategy(new NoopParentAttachmentStrategy());
} else if (node.isJointPrefetch()) {
node.setParentAttachmentStrategy(new StackLookupParentAttachmentStrategy(node));
} else if (node.getIncoming().getRelationship().isSourceIndependentFromTargetChange()) {
node.setParentAttachmentStrategy(new JoinedIdParentAttachementStrategy(context.getGraphManager(), node));
} else {
node.setParentAttachmentStrategy(new ResultScanParentAttachmentStrategy(node));
}
if (currentNode != null) {
currentNode.addChild(node);
}
node.afterInit();
// push node on stack
if (nodeStack.isEmpty()) {
root = node;
}
nodeStack.addLast(node);
return true;
}
PrefetchProcessorNode getParent() {
return (nodeStack.isEmpty()) ? null : nodeStack.getLast();
}
}