You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/06/08 00:19:41 UTC
(cayenne) 06/07: Merging ObjectContextQueryAction to DataContextQueryAction
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 9acb168b845aa3ac5dd5fe0bbfd42e31e634a8c0
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Jun 7 12:37:27 2026 -0400
Merging ObjectContextQueryAction to DataContextQueryAction
also moving ObjectContextGraphAction to DataContextGraphAction and mking non-public
---
.../org/apache/cayenne/access/DataContext.java | 5 +-
.../DataContextGraphAction.java} | 26 +-
.../cayenne/access/DataContextQueryAction.java | 375 +++++++++++++++++--
.../cayenne/util/ObjectContextQueryAction.java | 416 ---------------------
4 files changed, 346 insertions(+), 476 deletions(-)
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
index 4838db566..7b3298b52 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContext.java
@@ -57,7 +57,6 @@ import org.apache.cayenne.runtime.CayenneRuntime;
import org.apache.cayenne.tx.TransactionFactory;
import org.apache.cayenne.util.EventUtil;
import org.apache.cayenne.util.GenericResponse;
-import org.apache.cayenne.util.ObjectContextGraphAction;
import org.apache.cayenne.util.Util;
import java.io.IOException;
@@ -130,7 +129,7 @@ public class DataContext implements ObjectContext {
*
* @since 3.1
*/
- protected ObjectContextGraphAction graphAction;
+ protected DataContextGraphAction graphAction;
/**
* Stores user defined properties associated with this DataContext.
@@ -166,7 +165,7 @@ public class DataContext implements ObjectContext {
*/
public DataContext(DataChannel channel, ObjectStore objectStore) {
- graphAction = new ObjectContextGraphAction(this);
+ graphAction = new DataContextGraphAction(this);
objectCreator = new DataContextObjectCreator(this);
// inject self as parent context
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
similarity index 89%
rename from cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java
rename to cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
index fc31f84d2..1f0943202 100644
--- a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextGraphAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextGraphAction.java
@@ -16,9 +16,7 @@
* specific language governing permissions and limitations
* under the License.
****************************************************************/
-package org.apache.cayenne.util;
-
-import java.io.Serializable;
+package org.apache.cayenne.access;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.PersistenceState;
@@ -28,19 +26,14 @@ import org.apache.cayenne.reflect.ArcProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.PropertyDescriptor;
-/**
- * A base implementation of a helper class to handle
- * {@link ObjectContext#propertyChanged(org.apache.cayenne.Persistent, String, Object, Object)}
- * processing on behalf of an ObjectContext.
- *
- * @since 3.0
- * TODO: make this non-public!
- */
-public class ObjectContextGraphAction implements Serializable {
+import java.io.Serializable;
+
- protected ObjectContext context;
+class DataContextGraphAction implements Serializable {
- public ObjectContextGraphAction(ObjectContext context) {
+ private final ObjectContext context;
+
+ public DataContextGraphAction(ObjectContext context) {
this.context = context;
}
@@ -64,8 +57,7 @@ public class ObjectContextGraphAction implements Serializable {
if (property instanceof ArcProperty) {
handleArcPropertyChange(object, (ArcProperty) property, oldValue, newValue);
- }
- else {
+ } else {
handleSimplePropertyChange(object, propertyName, oldValue, newValue);
}
}
@@ -99,7 +91,7 @@ public class ObjectContextGraphAction implements Serializable {
String propertyName,
Object oldValue,
Object newValue) {
-
+
context.getGraphManager().nodePropertyChanged(
object.getObjectId(),
propertyName,
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
index 4222daa9f..39fa7b253 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataContextQueryAction.java
@@ -20,44 +20,175 @@
package org.apache.cayenne.access;
import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
+import org.apache.cayenne.QueryResponse;
+import org.apache.cayenne.cache.QueryCache;
+import org.apache.cayenne.cache.QueryCacheEntryFactory;
import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityInheritanceTree;
import org.apache.cayenne.query.EntityResultSegment;
+import org.apache.cayenne.query.IteratedQueryDecorator;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.query.RefreshQuery;
+import org.apache.cayenne.query.RelationshipQuery;
+import org.apache.cayenne.reflect.ArcProperty;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.GenericResponse;
import org.apache.cayenne.util.ListResponse;
-import org.apache.cayenne.util.ObjectContextQueryAction;
+import org.apache.cayenne.util.ShallowMergeOperation;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
-/**
- * A DataContext-specific version of
- * {@link org.apache.cayenne.util.ObjectContextQueryAction}.
- *
- * @since 1.2
- */
-class DataContextQueryAction extends ObjectContextQueryAction {
+class DataContextQueryAction {
- protected DataContext actingDataContext;
+ private static final boolean DONE = true;
- public DataContextQueryAction(DataContext actingContext, ObjectContext targetContext,
- Query query) {
- super(actingContext, targetContext, query);
- actingDataContext = actingContext;
+ private final ObjectContext actingContext;
+ private final ObjectContext targetContext;
+ private final DataContext actingDataContext;
+ private final Query query;
+ private final QueryMetadata metadata;
+ private final boolean queryOriginator;
+
+ private transient QueryResponse response;
+
+ public DataContextQueryAction(DataContext actingContext, ObjectContext targetContext, Query query) {
+
+ this.actingContext = actingContext;
+ this.actingDataContext = actingContext;
+ this.query = query;
+
+ // this means that a caller must pass self as both acting context and target
+ // context to indicate that a query originated here... null (ROP) or differing
+ // context indicates that the query was originated elsewhere, which has
+ // consequences in LOCAL_CACHE handling
+ this.queryOriginator = targetContext != null && targetContext == actingContext;
+
+ // no special target context and same target context as acting context mean the
+ // same thing. "normalize" the internal state to avoid confusion
+ this.targetContext = targetContext != actingContext ? targetContext : null;
+ this.metadata = query.getMetaData(actingContext.getEntityResolver());
}
- @Override
- protected boolean interceptInternalQuery() {
- return interceptObjectFromDataRowsQuery();
+ /**
+ * Worker method that performs internal query.
+ */
+ public QueryResponse execute() {
+ if (interceptIteratedQuery() != DONE) {
+ if (interceptOIDQuery() != DONE) {
+ if (interceptRelationshipQuery() != DONE) {
+ if (interceptRefreshQuery() != DONE) {
+ if (interceptLocalCache() != DONE) {
+ executePostCache();
+ }
+ }
+ }
+ }
+ }
+
+ interceptObjectConversion();
+ return response;
}
- private boolean interceptObjectFromDataRowsQuery() {
+ private boolean interceptIteratedQuery() {
+ if (query instanceof IteratedQueryDecorator) {
+ runQuery();
+ return DONE;
+ }
+ return !DONE;
+ }
+
+ private void executePostCache() {
+ if (interceptInternalQuery() != DONE) {
+ if (interceptPaginatedQuery() != DONE) {
+ runQuery();
+ }
+ }
+ }
+
+ /**
+ * Transfers fetched objects into the target context if it is different from "acting"
+ * context. Note that when this method is invoked, result objects are already
+ * registered with acting context by the parent channel.
+ */
+ protected void interceptObjectConversion() {
+
+ if (targetContext != null && !metadata.isFetchingDataRows()) {
+
+ // rewrite response to contain objects from the query context
+
+ GenericResponse childResponse = new GenericResponse();
+ ShallowMergeOperation merger = null;
+
+ for (response.reset(); response.next(); ) {
+ if (response.isList()) {
+ List<?> objects = response.currentList();
+ if (objects.isEmpty()) {
+ childResponse.addResultList(objects);
+ } else {
+
+ // minor optimization, skip Object[] if there are no persistent objects
+ boolean haveObjects = metadata.getResultSetMapping() == null;
+ if (!haveObjects) {
+ for (Object next : metadata.getResultSetMapping()) {
+ if (next instanceof EntityResultSegment) {
+ haveObjects = true;
+ break;
+ }
+ }
+ }
+
+ if (merger == null) {
+ merger = new ShallowMergeOperation(targetContext);
+ }
+
+ // TODO: Andrus 1/31/2006 - IncrementalFaultList is not properly
+ // transferred between contexts....
+
+ List<Object> childObjects = new ArrayList<>(objects.size());
+ for (Object object1 : objects) {
+ if (object1 instanceof Persistent object) {
+ childObjects.add(merger.merge(object));
+ } else if (haveObjects && object1 instanceof Object[] parentData) {
+ // merge objects inside Object[]
+ Object[] childData = new Object[parentData.length];
+ System.arraycopy(parentData, 0, childData, 0, parentData.length);
+ for (int i = 0; i < childData.length; i++) {
+ if (childData[i] instanceof Persistent) {
+ childData[i] = merger.merge((Persistent) childData[i]);
+ }
+ }
+ childObjects.add(childData);
+ } else {
+ childObjects.add(object1);
+ }
+ }
+
+ childResponse.addResultList(childObjects);
+ }
+ } else {
+ childResponse.addBatchUpdateCount(response.currentUpdateCount());
+ }
+ }
+
+ response = childResponse;
+ }
+
+ }
+
+ protected boolean interceptInternalQuery() {
if (query instanceof ObjectsFromDataRowsQuery objectsFromDataRowsQuery) {
response = new ListResponse(actingDataContext.objectsFromDataRows(
objectsFromDataRowsQuery.getDescriptor(),
@@ -68,9 +199,8 @@ class DataContextQueryAction extends ObjectContextQueryAction {
}
/**
- * Overrides super implementation to property handle data row fetches.
+ * Handles {@link ObjectIdQuery}, properly handling data row fetches.
*/
- @Override
protected boolean interceptOIDQuery() {
if (query instanceof ObjectIdQuery oidQuery) {
@@ -79,10 +209,10 @@ class DataContextQueryAction extends ObjectContextQueryAction {
if (object != null) {
// TODO: andrus, 10/14/2006 - obtaining a row from an object is the
- // only piece that makes this method different from the super
- // implementation. This is used in NEW objects sorting on insert. It
- // would be nice to implement an alternative algorithm that wouldn't
- // require this hack.
+ // only piece that makes this method different from a plain cache
+ // lookup. This is used in NEW objects sorting on insert. It would be
+ // nice to implement an alternative algorithm that wouldn't require
+ // this hack.
if (oidQuery.isFetchingDataRows()) {
object = actingDataContext.currentSnapshot((Persistent) object);
}
@@ -100,7 +230,116 @@ class DataContextQueryAction extends ObjectContextQueryAction {
return !DONE;
}
- @Override
+ // TODO: bunch of copy/paset from DataDomainQueryAction
+ protected Object polymorphicObjectFromCache(ObjectId superOid) {
+ Object object = actingContext.getGraphManager().getNode(superOid);
+ if (object != null) {
+ return object;
+ }
+
+ EntityInheritanceTree inheritanceTree = actingContext.getEntityResolver().getInheritanceTree(superOid.getEntityName());
+ if (!inheritanceTree.getChildren().isEmpty()) {
+ object = polymorphicObjectFromCache(inheritanceTree, superOid);
+ }
+
+ return object;
+ }
+
+ private Object polymorphicObjectFromCache(EntityInheritanceTree superNode, ObjectId superOid) {
+
+ for (EntityInheritanceTree child : superNode.getChildren()) {
+ ObjectId id = ObjectId.of(child.getEntity().getName(), superOid);
+ Object object = actingContext.getGraphManager().getNode(id);
+ if (object != null) {
+ return object;
+ }
+
+ object = polymorphicObjectFromCache(child, superOid);
+ if (object != null) {
+ return object;
+ }
+ }
+
+ return null;
+ }
+
+ protected boolean interceptRelationshipQuery() {
+
+ if (query instanceof RelationshipQuery relationshipQuery) {
+ if (!relationshipQuery.isRefreshing()) {
+
+ // don't intercept to-many relationships if fetch is done to the same
+ // context as the root context of this action - this will result in an
+ // infinite loop.
+
+ if (targetContext == null
+ && relationshipQuery.getRelationship(
+ actingContext.getEntityResolver()).isToMany()) {
+ return !DONE;
+ }
+
+ ObjectId id = relationshipQuery.getObjectId();
+ Object object = actingContext.getGraphManager().getNode(id);
+
+ if (object != null) {
+
+ ClassDescriptor descriptor = actingContext
+ .getEntityResolver()
+ .getClassDescriptor(id.getEntityName());
+
+ if (!descriptor.isFault(object)) {
+
+ ArcProperty property = (ArcProperty) descriptor
+ .getProperty(relationshipQuery.getRelationshipName());
+
+ if (!property.isFault(object)) {
+
+ Object related = property.readPropertyDirectly(object);
+
+ // null to-one
+ List<?> result = switch (related) {
+ case null -> new ArrayList<>(1);
+
+ // to-many List
+ case List list -> list;
+
+ // to-many Set
+ case Set set -> new ArrayList<>(set);
+
+ // to-many Map
+ case Map map -> new ArrayList<>(map.values());
+
+ // non-null to-one
+ // TODO: any risks of returning an immutable list here?
+ default -> Collections.singletonList(related);
+ };
+
+ this.response = new ListResponse(result);
+ return DONE;
+
+ }
+
+ // Workaround for CAY-1183. If a Relationship query is being sent
+ // from child context, we assure that local object is not NEW and
+ // relationship - unresolved (this way exception will occur). This
+ // helps when faulting objects that were committed to parent
+ // context (this), but not to database. Checking type of context's
+ // channel is the only way to ensure that we are on the top level
+ // of context hierarchy (there might be more than one-level-deep
+ // nested contexts).
+ if (((Persistent) object).getPersistenceState() == PersistenceState.NEW
+ && !(actingContext.getParent() instanceof ObjectContext)) {
+ this.response = new ListResponse();
+ return DONE;
+ }
+ }
+ }
+ }
+ }
+
+ return !DONE;
+ }
+
protected boolean interceptPaginatedQuery() {
if (metadata.getPageSize() > 0) {
// this will select raw ids
@@ -121,7 +360,7 @@ class DataContextQueryAction extends ObjectContextQueryAction {
private IncrementalFaultList<?> createIncrementalFaultList(List<?> rawIds, int maxIdQualifierSize) {
// just a sanity check
Objects.requireNonNull(rawIds, "Trying to execute paginated query that is not a select query");
- if(isMixedResultsForPaginatedQuery()) {
+ if (isMixedResultsForPaginatedQuery()) {
return new MixedResultIncrementalFaultList<>(actingDataContext, query, maxIdQualifierSize, rawIds);
} else {
DbEntity dbEntity = metadata.getDbEntity();
@@ -136,10 +375,10 @@ class DataContextQueryAction extends ObjectContextQueryAction {
private boolean isMixedResultsForPaginatedQuery() {
boolean mixedResults = false;
List<Object> rsMapping = metadata.getResultSetMapping();
- if(rsMapping != null) {
- if(rsMapping.size() > 1) {
+ if (rsMapping != null) {
+ if (rsMapping.size() > 1) {
mixedResults = true;
- } else if(rsMapping.size() == 1) {
+ } else if (rsMapping.size() == 1) {
mixedResults = !(rsMapping.getFirst() instanceof EntityResultSegment)
|| !metadata.isSingleResultSetMapping();
}
@@ -147,24 +386,21 @@ class DataContextQueryAction extends ObjectContextQueryAction {
return mixedResults;
}
- @Override
protected boolean interceptRefreshQuery() {
if (query instanceof RefreshQuery refreshQuery) {
- DataContext context = (DataContext) actingContext;
-
// handle four separate cases, but do not combine them as it will be
// unclear how to handle cascading behavior
// 1. refresh all
if (refreshQuery.isRefreshAll()) {
- synchronized (context.getObjectStore()) {
+ synchronized (actingDataContext.getObjectStore()) {
- invalidateLocally(context.getObjectStore(), context
+ invalidateLocally(actingDataContext.getObjectStore(), actingDataContext
.getObjectStore()
.getObjectIterator());
- context.getQueryCache().clear();
+ actingDataContext.getQueryCache().clear();
}
// cascade
@@ -175,8 +411,8 @@ class DataContextQueryAction extends ObjectContextQueryAction {
Collection<?> objects = refreshQuery.getObjects();
if (objects != null && !objects.isEmpty()) {
- synchronized (context.getObjectStore()) {
- invalidateLocally(context.getObjectStore(), objects.iterator());
+ synchronized (actingDataContext.getObjectStore()) {
+ invalidateLocally(actingDataContext.getObjectStore(), objects.iterator());
}
// cascade
@@ -188,11 +424,11 @@ class DataContextQueryAction extends ObjectContextQueryAction {
if (cachedQuery != null) {
String cacheKey = cachedQuery
- .getMetaData(context.getEntityResolver())
+ .getMetaData(actingDataContext.getEntityResolver())
.getCacheKey();
- context.getQueryCache().remove(cacheKey);
+ actingDataContext.getQueryCache().remove(cacheKey);
- this.response = context.performGenericQuery(cachedQuery);
+ this.response = actingDataContext.performGenericQuery(cachedQuery);
// do not cascade to avoid running query twice
return DONE;
@@ -203,7 +439,7 @@ class DataContextQueryAction extends ObjectContextQueryAction {
if (groups != null && groups.length > 0) {
for (String group : groups) {
- context.getQueryCache().removeGroup(group);
+ actingDataContext.getQueryCache().removeGroup(group);
}
// cascade group invalidation
@@ -240,4 +476,63 @@ class DataContextQueryAction extends ObjectContextQueryAction {
object.setPersistenceState(PersistenceState.HOLLOW);
}
}
+
+ protected boolean interceptLocalCache() {
+
+ if (metadata.getCacheKey() == null) {
+ return !DONE;
+ }
+
+ // ignore local cache unless this context originated the query...
+ if (!queryOriginator) {
+ return !DONE;
+ }
+
+ boolean cache = QueryCacheStrategy.LOCAL_CACHE == metadata.getCacheStrategy();
+ boolean cacheOrCacheRefresh = cache
+ || QueryCacheStrategy.LOCAL_CACHE_REFRESH == metadata.getCacheStrategy();
+
+ if (!cacheOrCacheRefresh) {
+ return !DONE;
+ }
+
+ QueryCache queryCache = getQueryCache();
+ QueryCacheEntryFactory factory = getCacheObjectFactory();
+
+ if (cache) {
+ boolean wasResponseNull = (response == null);
+ List cachedResults = queryCache.get(metadata, factory);
+
+ // response may already be initialized by the factory above ... it is null if
+ // there was a preexisting cache entry
+ if (response == null || wasResponseNull) {
+ response = new ListResponse(cachedResults);
+ }
+ } else {
+ // on cache-refresh request, fetch without blocking and fill the cache
+ queryCache.put(metadata, factory.createObject());
+ }
+
+ return DONE;
+ }
+
+ protected QueryCache getQueryCache() {
+ return actingDataContext.getQueryCache();
+ }
+
+ protected QueryCacheEntryFactory getCacheObjectFactory() {
+ return () -> {
+ executePostCache();
+ List result = response.firstList();
+ // make an immutable list to make sure callers don't mess it up
+ return result != null ? Collections.unmodifiableList(result) : null;
+ };
+ }
+
+ /**
+ * Fetches data from the channel.
+ */
+ protected void runQuery() {
+ this.response = actingContext.getParent().onQuery(actingContext, query);
+ }
}
diff --git a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java b/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java
deleted file mode 100644
index 05cbed1d1..000000000
--- a/cayenne/src/main/java/org/apache/cayenne/util/ObjectContextQueryAction.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*****************************************************************
- * 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.util;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.PersistenceState;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.cache.QueryCache;
-import org.apache.cayenne.cache.QueryCacheEntryFactory;
-import org.apache.cayenne.map.EntityInheritanceTree;
-import org.apache.cayenne.query.*;
-import org.apache.cayenne.reflect.ArcProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-
-/**
- * A helper class that implements
- * {@link org.apache.cayenne.DataChannel#onQuery(ObjectContext, Query)} logic on behalf of
- * an ObjectContext.
- * <p>
- * <i>Intended for internal use only.</i>
- * </p>
- *
- * @since 1.2
- */
-public abstract class ObjectContextQueryAction {
-
- protected static final boolean DONE = true;
-
- protected ObjectContext targetContext;
- protected ObjectContext actingContext;
- protected Query query;
- protected QueryMetadata metadata;
- protected boolean queryOriginator;
-
- protected transient QueryResponse response;
-
- public ObjectContextQueryAction(ObjectContext actingContext,
- ObjectContext targetContext, Query query) {
-
- this.actingContext = actingContext;
- this.query = query;
-
- // this means that a caller must pass self as both acting context and target
- // context to indicate that a query originated here... null (ROP) or differing
- // context indicates that the query was originated elsewhere, which has
- // consequences in LOCAL_CACHE handling
- this.queryOriginator = targetContext != null && targetContext == actingContext;
-
- // no special target context and same target context as acting context mean the
- // same thing. "normalize" the internal state to avoid confusion
- this.targetContext = targetContext != actingContext ? targetContext : null;
- this.metadata = query.getMetaData(actingContext.getEntityResolver());
- }
-
- /**
- * Worker method that performs internal query.
- */
- public QueryResponse execute() {
- if (interceptIteratedQuery() != DONE) {
- if (interceptOIDQuery() != DONE) {
- if (interceptRelationshipQuery() != DONE) {
- if (interceptRefreshQuery() != DONE) {
- if (interceptLocalCache() != DONE) {
- executePostCache();
- }
- }
- }
- }
- }
-
- interceptObjectConversion();
- return response;
- }
-
- private boolean interceptIteratedQuery() {
- if (query instanceof IteratedQueryDecorator) {
- runQuery();
- return DONE;
- }
- return !DONE;
- }
-
- private void executePostCache() {
- if (interceptInternalQuery() != DONE) {
- if (interceptPaginatedQuery() != DONE) {
- runQuery();
- }
- }
- }
-
- /**
- * Transfers fetched objects into the target context if it is different from "acting"
- * context. Note that when this method is invoked, result objects are already
- * registered with acting context by the parent channel.
- */
- protected void interceptObjectConversion() {
-
- if (targetContext != null && !metadata.isFetchingDataRows()) {
-
- // rewrite response to contain objects from the query context
-
- GenericResponse childResponse = new GenericResponse();
- ShallowMergeOperation merger = null;
-
- for (response.reset(); response.next();) {
- if (response.isList()) {
- List objects = response.currentList();
- if (objects.isEmpty()) {
- childResponse.addResultList(objects);
- } else {
-
- // minor optimization, skip Object[] if there are no persistent objects
- boolean haveObjects = metadata.getResultSetMapping() == null;
- if(!haveObjects) {
- for (Object next : metadata.getResultSetMapping()) {
- if(next instanceof EntityResultSegment) {
- haveObjects = true;
- break;
- }
- }
- }
-
- if (merger == null) {
- merger = new ShallowMergeOperation(targetContext);
- }
-
- // TODO: Andrus 1/31/2006 - IncrementalFaultList is not properly
- // transferred between contexts....
-
- List<Object> childObjects = new ArrayList<>(objects.size());
- for (Object object1 : objects) {
- if(object1 instanceof Persistent) {
- Persistent object = (Persistent) object1;
- childObjects.add(merger.merge(object));
- } else if(haveObjects && object1 instanceof Object[]) {
- // merge objects inside Object[]
- Object[] parentData = (Object[]) object1;
- Object[] childData = new Object[parentData.length];
- System.arraycopy(parentData, 0, childData, 0, parentData.length);
- for(int i=0; i<childData.length; i++) {
- if(childData[i] instanceof Persistent) {
- childData[i] = merger.merge((Persistent)childData[i]);
- }
- }
- childObjects.add(childData);
- } else {
- childObjects.add(object1);
- }
- }
-
- childResponse.addResultList(childObjects);
- }
- }
- else {
- childResponse.addBatchUpdateCount(response.currentUpdateCount());
- }
- }
-
- response = childResponse;
- }
-
- }
-
- protected boolean interceptInternalQuery() {
- return !DONE;
- }
-
- protected boolean interceptOIDQuery() {
- if (query instanceof ObjectIdQuery) {
- ObjectIdQuery oidQuery = (ObjectIdQuery) query;
-
- if (!oidQuery.isFetchMandatory() && !oidQuery.isFetchingDataRows()) {
- Object object = polymorphicObjectFromCache(
- oidQuery.getObjectId());
- if (object != null) {
-
- // do not return hollow objects
- if (((Persistent) object).getPersistenceState() == PersistenceState.HOLLOW) {
- return !DONE;
- }
-
- this.response = new ListResponse(object);
- return DONE;
- }
- }
- }
-
- return !DONE;
- }
-
- // TODO: bunch of copy/paset from DataDomainQueryAction
- protected Object polymorphicObjectFromCache(ObjectId superOid) {
- Object object = actingContext.getGraphManager().getNode(superOid);
- if (object != null) {
- return object;
- }
-
- EntityInheritanceTree inheritanceTree = actingContext.getEntityResolver().getInheritanceTree(superOid.getEntityName());
- if (!inheritanceTree.getChildren().isEmpty()) {
- object = polymorphicObjectFromCache(inheritanceTree, superOid);
- }
-
- return object;
- }
-
- private Object polymorphicObjectFromCache(EntityInheritanceTree superNode, ObjectId superOid) {
-
- for (EntityInheritanceTree child : superNode.getChildren()) {
- ObjectId id = ObjectId.of(child.getEntity().getName(), superOid);
- Object object = actingContext.getGraphManager().getNode(id);
- if (object != null) {
- return object;
- }
-
- object = polymorphicObjectFromCache(child, superOid);
- if (object != null) {
- return object;
- }
- }
-
- return null;
- }
-
- protected boolean interceptRelationshipQuery() {
-
- if (query instanceof RelationshipQuery) {
- RelationshipQuery relationshipQuery = (RelationshipQuery) query;
- if (!relationshipQuery.isRefreshing()) {
-
- // don't intercept to-many relationships if fetch is done to the same
- // context as the root context of this action - this will result in an
- // infinite loop.
-
- if (targetContext == null
- && relationshipQuery.getRelationship(
- actingContext.getEntityResolver()).isToMany()) {
- return !DONE;
- }
-
- ObjectId id = relationshipQuery.getObjectId();
- Object object = actingContext.getGraphManager().getNode(id);
-
- if (object != null) {
-
- ClassDescriptor descriptor = actingContext
- .getEntityResolver()
- .getClassDescriptor(id.getEntityName());
-
- if (!descriptor.isFault(object)) {
-
- ArcProperty property = (ArcProperty) descriptor
- .getProperty(relationshipQuery.getRelationshipName());
-
- if (!property.isFault(object)) {
-
- Object related = property.readPropertyDirectly(object);
-
- List result;
-
- // null to-one
- if (related == null) {
- result = new ArrayList(1);
- }
- // to-many List
- else if (related instanceof List) {
- result = (List) related;
- }
- // to-many Set
- else if (related instanceof Set) {
- result = new ArrayList((Set) related);
- }
- // to-many Map
- else if (related instanceof Map) {
- result = new ArrayList(((Map) related).values());
- }
- // non-null to-one
- else {
- result = new ArrayList(1);
- result.add(related);
- }
-
- this.response = new ListResponse(result);
- return DONE;
-
- }
-
- /**
- * Workaround for CAY-1183. If a Relationship query is being sent
- * from child context, we assure that local object is not NEW and
- * relationship - unresolved (this way exception will occur). This
- * helps when faulting objects that were committed to parent
- * context (this), but not to database. Checking type of context's
- * channel is the only way to ensure that we are on the top level
- * of context hierarchy (there might be more than one-level-deep
- * nested contexts).
- */
- if (((Persistent) object).getPersistenceState() == PersistenceState.NEW
- && !(actingContext.getParent() instanceof ObjectContext)) {
- this.response = new ListResponse();
- return DONE;
- }
- }
- }
- }
- }
-
- return !DONE;
- }
-
- /**
- * @since 3.0
- */
- protected abstract boolean interceptPaginatedQuery();
-
- /**
- * @since 3.0
- */
- protected abstract boolean interceptRefreshQuery();
-
- /**
- * @since 3.0
- */
- protected boolean interceptLocalCache() {
-
- if (metadata.getCacheKey() == null) {
- return !DONE;
- }
-
- // ignore local cache unless this context originated the query...
- if (!queryOriginator) {
- return !DONE;
- }
-
- boolean cache = QueryCacheStrategy.LOCAL_CACHE == metadata.getCacheStrategy();
- boolean cacheOrCacheRefresh = cache
- || QueryCacheStrategy.LOCAL_CACHE_REFRESH == metadata.getCacheStrategy();
-
- if (!cacheOrCacheRefresh) {
- return !DONE;
- }
-
- QueryCache queryCache = getQueryCache();
- QueryCacheEntryFactory factory = getCacheObjectFactory();
-
- if (cache) {
- boolean wasResponseNull = (response == null);
- List cachedResults = queryCache.get(metadata, factory);
-
- // response may already be initialized by the factory above ... it is null if
- // there was a preexisting cache entry
- if (response == null || wasResponseNull) {
- response = new ListResponse(cachedResults);
- }
- } else {
- // on cache-refresh request, fetch without blocking and fill the cache
- queryCache.put(metadata, factory.createObject());
- }
-
- return DONE;
- }
-
- /**
- * @since 3.0
- */
- protected QueryCache getQueryCache() {
- return ((DataContext) actingContext).getQueryCache();
- }
-
- /**
- * @since 3.0
- */
- protected QueryCacheEntryFactory getCacheObjectFactory() {
- return new QueryCacheEntryFactory() {
-
- public List createObject() {
- executePostCache();
- List result = response.firstList();
- // make an immutable list to make sure callers don't mess it up
- return result != null ? Collections.unmodifiableList(result) : null;
- }
- };
- }
-
- /**
- * Fetches data from the channel.
- */
- protected void runQuery() {
- this.response = actingContext.getParent().onQuery(actingContext, query);
- }
-}